69e7a80fce2ec99d3ccb0232f4d1ad6518a15f48
[scons.git] / src / test_interrupts.py
1 #!/usr/bin/env python
2 #
3 # __COPYRIGHT__
4 #
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:
12 #
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
15 #
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.
23 #
24 from __future__ import generators  ### KEEP FOR COMPATIBILITY FIXERS
25
26 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
27
28 """
29 Verify that the SCons source code contains only correct handling of
30 keyboard interrupts (e.g. Ctrl-C).
31 """
32
33 import os
34 import os.path
35 import re
36 import time
37
38 import TestSCons
39
40 test = TestSCons.TestSCons()
41
42 # We do not want statements of the form:
43 # try:
44 #   # do something, e.g.
45 #   return a['x']
46 # except:
47 #   # do the exception handling
48 #   a['x'] = getx()
49 #   return a['x']
50 #
51 # The code above may catch a KeyboardInterrupt exception, which was not
52 # intended by the programmer. We check for these situations in all python
53 # source files.
54
55 try:
56     cwd = os.environ['SCONS_CWD']
57 except KeyError:
58     scons_lib_dir = os.environ['SCONS_LIB_DIR']
59     MANIFEST = os.path.join(scons_lib_dir, 'MANIFEST.in')
60 else:
61     #cwd = os.getcwd()
62     scons_lib_dir = os.path.join(cwd, 'build', 'scons')
63     MANIFEST = os.path.join(scons_lib_dir, 'MANIFEST')
64
65 # We expect precisely this many uncaught KeyboardInterrupt exceptions
66 # from the files in the following dictionary.
67
68 expected_uncaught = {
69     'engine/SCons/Job.py' :             5,
70     'engine/SCons/Script/Main.py' :     1,
71     'engine/SCons/Taskmaster.py' :      3,
72 }
73
74 try:
75     fp = open(MANIFEST)
76 except IOError:
77     test.skip_test('%s does not exist; skipping test.\n' % MANIFEST)
78 else:
79     files = fp.read().split()
80     files = [f for f in files if f[-3:] == '.py']
81
82 # some regexps to parse the python files
83 tryexc_pat = re.compile(
84 r'^(?P<try_or_except>(?P<indent> *)(try|except)( [^\n]*)?:.*)',re.MULTILINE)
85 keyboardint_pat = re.compile(r' *except +([^,],)*KeyboardInterrupt([ ,][^\n]*)?:[^\n]*')
86 exceptall_pat   = re.compile(r' *except(?: *| +Exception *, *[^: ]+):[^\n]*')
87
88 uncaughtKeyboardInterrupt = 0
89 for f in files:
90     contents = open(os.path.join(scons_lib_dir, f)).read()
91     try_except_lines = {}
92     lastend = 0
93     while 1:
94         match = tryexc_pat.search( contents, lastend )
95         if match is None:
96             break
97         #print match.groups()
98         lastend = match.end()
99         try:
100             indent_list = try_except_lines[match.group('indent')]
101         except:
102             indent_list = []
103         line_num = 1 + contents[:match.start()].count('\n')
104         indent_list.append( (line_num, match.group('try_or_except') ) )
105         try_except_lines[match.group('indent')] = indent_list
106     uncaught_this_file = []
107     for indent in try_except_lines.keys():
108         exc_keyboardint_seen = 0
109         exc_all_seen = 0
110         for (l,statement) in try_except_lines[indent] + [(-1,indent + 'try')]:
111             #print "%4d %s" % (l,statement),
112             m1 = keyboardint_pat.match(statement)
113             m2 = exceptall_pat.match(statement)
114             if statement.find(indent + 'try') == 0:
115                 if exc_all_seen and not exc_keyboardint_seen:
116                     uncaught_this_file.append(line)
117                 exc_keyboardint_seen = 0
118                 exc_all_seen = 0
119                 line = l
120                 #print " -> reset"
121             elif m1 is not None:
122                 exc_keyboardint_seen = 1
123                 #print " -> keyboard -> ", m1.groups()
124             elif m2 is not None:
125                 exc_all_seen = 1
126                 #print " -> all -> ", m2.groups()
127             else:
128                 pass
129                 #print "Warning: unknown statement %s" % statement
130     expected_num = expected_uncaught.get(f, 0)
131     if expected_num != len(uncaught_this_file):
132         uncaughtKeyboardInterrupt = 1
133         msg = "%s:  expected %d uncaught interrupts, got %d:"
134         print msg % (f, expected_num, len(uncaught_this_file))
135         for line in uncaught_this_file:
136             print "  File %s:%d: Uncaught KeyboardInterrupt!" % (f,line)
137
138 test.fail_test(uncaughtKeyboardInterrupt)
139
140 test.pass_test()
141
142 # Local Variables:
143 # tab-width:4
144 # indent-tabs-mode:nil
145 # End:
146 # vim: set expandtab tabstop=4 shiftwidth=4: