blob: 4544c4cb72d2fc0a96d1ed988f0373c8155048c4 [file] [log] [blame]
Victor Stinner024e37a2011-03-31 01:31:06 +02001from contextlib import contextmanager
2import faulthandler
3import re
4import signal
5import subprocess
6import sys
7from test import support, script_helper
8import tempfile
9import unittest
10
11try:
12 from resource import setrlimit, RLIMIT_CORE, error as resource_error
13except ImportError:
14 prepare_subprocess = None
15else:
16 def prepare_subprocess():
17 # don't create core file
18 try:
19 setrlimit(RLIMIT_CORE, (0, 0))
20 except (ValueError, resource_error):
21 pass
22
23def expected_traceback(lineno1, lineno2, header, count=1):
24 regex = header
25 regex += r' File "\<string\>", line %s in func\n' % lineno1
26 regex += r' File "\<string\>", line %s in \<module\>' % lineno2
27 if count != 1:
28 regex = (regex + '\n') * (count - 1) + regex
29 return '^' + regex + '$'
30
31@contextmanager
32def temporary_filename():
33 filename = tempfile.mktemp()
34 try:
35 yield filename
36 finally:
37 support.unlink(filename)
38
39class FaultHandlerTests(unittest.TestCase):
Victor Stinner05585cb2011-03-31 13:29:56 +020040 def get_output(self, code, filename=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020041 """
42 Run the specified code in Python (in a new child process) and read the
43 output from the standard error or from a file (if filename is set).
44 Return the output lines as a list.
45
46 Strip the reference count from the standard error for Python debug
47 build, and replace "Current thread 0x00007f8d8fbd9700" by "Current
48 thread XXX".
49 """
50 options = {}
51 if prepare_subprocess:
52 options['preexec_fn'] = prepare_subprocess
53 process = script_helper.spawn_python('-c', code, **options)
54 stdout, stderr = process.communicate()
55 exitcode = process.wait()
Victor Stinner024e37a2011-03-31 01:31:06 +020056 if filename:
57 with open(filename, "rb") as fp:
58 output = fp.read()
59 else:
60 output = support.strip_python_stderr(stdout)
61 output = output.decode('ascii', 'backslashreplace')
62 output = re.sub('Current thread 0x[0-9a-f]+',
63 'Current thread XXX',
64 output)
Victor Stinner05585cb2011-03-31 13:29:56 +020065 return output.splitlines(), exitcode
Victor Stinner024e37a2011-03-31 01:31:06 +020066
67 def check_fatal_error(self, code, line_number, name_regex,
Victor Stinnerf0480752011-03-31 11:34:08 +020068 filename=None, all_threads=False, other_regex=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020069 """
70 Check that the fault handler for fatal errors is enabled and check the
71 traceback from the child process output.
72
73 Raise an error if the output doesn't match the expected format.
74 """
75 if all_threads:
76 header = 'Current thread XXX'
77 else:
78 header = 'Traceback (most recent call first)'
79 regex = """
80^Fatal Python error: {name}
81
82{header}:
83 File "<string>", line {lineno} in <module>$
84""".strip()
85 regex = regex.format(
86 lineno=line_number,
87 name=name_regex,
88 header=re.escape(header))
Victor Stinnerf0480752011-03-31 11:34:08 +020089 if other_regex:
90 regex += '|' + other_regex
Victor Stinner05585cb2011-03-31 13:29:56 +020091 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +020092 output = '\n'.join(output)
93 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +020094 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +020095
96 def test_read_null(self):
97 self.check_fatal_error("""
98import faulthandler
99faulthandler.enable()
100faulthandler._read_null()
101""".strip(),
102 3,
103 '(?:Segmentation fault|Bus error)')
104
105 def test_sigsegv(self):
106 self.check_fatal_error("""
107import faulthandler
108faulthandler.enable()
109faulthandler._sigsegv()
110""".strip(),
111 3,
112 'Segmentation fault')
113
114 @unittest.skipIf(sys.platform == 'win32',
115 "SIGFPE cannot be caught on Windows")
116 def test_sigfpe(self):
117 self.check_fatal_error("""
118import faulthandler
119faulthandler.enable()
120faulthandler._sigfpe()
121""".strip(),
122 3,
123 'Floating point exception')
124
125 @unittest.skipIf(not hasattr(faulthandler, '_sigbus'),
126 "need faulthandler._sigbus()")
127 def test_sigbus(self):
128 self.check_fatal_error("""
129import faulthandler
130faulthandler.enable()
131faulthandler._sigbus()
132""".strip(),
133 3,
134 'Bus error')
135
136 @unittest.skipIf(not hasattr(faulthandler, '_sigill'),
137 "need faulthandler._sigill()")
138 def test_sigill(self):
139 self.check_fatal_error("""
140import faulthandler
141faulthandler.enable()
142faulthandler._sigill()
143""".strip(),
144 3,
145 'Illegal instruction')
146
147 def test_fatal_error(self):
148 self.check_fatal_error("""
149import faulthandler
150faulthandler._fatal_error(b'xyz')
151""".strip(),
152 2,
153 'xyz')
154
Victor Stinner024e37a2011-03-31 01:31:06 +0200155 @unittest.skipIf(not hasattr(faulthandler, '_stack_overflow'),
156 'need faulthandler._stack_overflow()')
157 def test_stack_overflow(self):
158 self.check_fatal_error("""
159import faulthandler
160faulthandler.enable()
161faulthandler._stack_overflow()
162""".strip(),
163 3,
Victor Stinnerf0480752011-03-31 11:34:08 +0200164 '(?:Segmentation fault|Bus error)',
165 other_regex='unable to raise a stack overflow')
Victor Stinner024e37a2011-03-31 01:31:06 +0200166
167 def test_gil_released(self):
168 self.check_fatal_error("""
169import faulthandler
170faulthandler.enable()
171faulthandler._read_null(True)
172""".strip(),
173 3,
174 '(?:Segmentation fault|Bus error)')
175
176 def test_enable_file(self):
177 with temporary_filename() as filename:
178 self.check_fatal_error("""
179import faulthandler
180output = open({filename}, 'wb')
181faulthandler.enable(output)
182faulthandler._read_null(True)
183""".strip().format(filename=repr(filename)),
184 4,
185 '(?:Segmentation fault|Bus error)',
186 filename=filename)
187
188 def test_enable_threads(self):
189 self.check_fatal_error("""
190import faulthandler
191faulthandler.enable(all_threads=True)
192faulthandler._read_null(True)
193""".strip(),
194 3,
195 '(?:Segmentation fault|Bus error)',
196 all_threads=True)
197
198 def test_disable(self):
199 code = """
200import faulthandler
201faulthandler.enable()
202faulthandler.disable()
203faulthandler._read_null()
204""".strip()
205 not_expected = 'Fatal Python error'
Victor Stinner05585cb2011-03-31 13:29:56 +0200206 stderr, exitcode = self.get_output(code)
Victor Stinner024e37a2011-03-31 01:31:06 +0200207 stder = '\n'.join(stderr)
208 self.assertTrue(not_expected not in stderr,
209 "%r is present in %r" % (not_expected, stderr))
Victor Stinner05585cb2011-03-31 13:29:56 +0200210 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200211
212 def test_is_enabled(self):
213 was_enabled = faulthandler.is_enabled()
214 try:
215 faulthandler.enable()
216 self.assertTrue(faulthandler.is_enabled())
217 faulthandler.disable()
218 self.assertFalse(faulthandler.is_enabled())
219 finally:
220 if was_enabled:
221 faulthandler.enable()
222 else:
223 faulthandler.disable()
224
225 def check_dump_traceback(self, filename):
226 """
227 Explicitly call dump_traceback() function and check its output.
228 Raise an error if the output doesn't match the expected format.
229 """
230 code = """
231import faulthandler
232
233def funcB():
234 if {has_filename}:
235 with open({filename}, "wb") as fp:
236 faulthandler.dump_traceback(fp)
237 else:
238 faulthandler.dump_traceback()
239
240def funcA():
241 funcB()
242
243funcA()
244""".strip()
245 code = code.format(
246 filename=repr(filename),
247 has_filename=bool(filename),
248 )
249 if filename:
250 lineno = 6
251 else:
252 lineno = 8
253 expected = [
254 'Traceback (most recent call first):',
255 ' File "<string>", line %s in funcB' % lineno,
256 ' File "<string>", line 11 in funcA',
257 ' File "<string>", line 13 in <module>'
258 ]
Victor Stinner05585cb2011-03-31 13:29:56 +0200259 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200260 self.assertEqual(trace, expected)
Victor Stinner05585cb2011-03-31 13:29:56 +0200261 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200262
263 def test_dump_traceback(self):
264 self.check_dump_traceback(None)
265 with temporary_filename() as filename:
266 self.check_dump_traceback(filename)
267
268 def check_dump_traceback_threads(self, filename):
269 """
270 Call explicitly dump_traceback(all_threads=True) and check the output.
271 Raise an error if the output doesn't match the expected format.
272 """
273 code = """
274import faulthandler
275from threading import Thread, Event
276import time
277
278def dump():
279 if {filename}:
280 with open({filename}, "wb") as fp:
281 faulthandler.dump_traceback(fp, all_threads=True)
282 else:
283 faulthandler.dump_traceback(all_threads=True)
284
285class Waiter(Thread):
286 # avoid blocking if the main thread raises an exception.
287 daemon = True
288
289 def __init__(self):
290 Thread.__init__(self)
291 self.running = Event()
292 self.stop = Event()
293
294 def run(self):
295 self.running.set()
296 self.stop.wait()
297
298waiter = Waiter()
299waiter.start()
300waiter.running.wait()
301dump()
302waiter.stop.set()
303waiter.join()
304""".strip()
305 code = code.format(filename=repr(filename))
Victor Stinner05585cb2011-03-31 13:29:56 +0200306 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200307 output = '\n'.join(output)
308 if filename:
309 lineno = 8
310 else:
311 lineno = 10
312 regex = """
313^Thread 0x[0-9a-f]+:
314(?: File ".*threading.py", line [0-9]+ in wait
315)? File ".*threading.py", line [0-9]+ in wait
316 File "<string>", line 23 in run
317 File ".*threading.py", line [0-9]+ in _bootstrap_inner
318 File ".*threading.py", line [0-9]+ in _bootstrap
319
320Current thread XXX:
321 File "<string>", line {lineno} in dump
322 File "<string>", line 28 in <module>$
323""".strip()
324 regex = regex.format(lineno=lineno)
325 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200326 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200327
328 def test_dump_traceback_threads(self):
329 self.check_dump_traceback_threads(None)
330 with temporary_filename() as filename:
331 self.check_dump_traceback_threads(filename)
332
333 def _check_dump_tracebacks_later(self, repeat, cancel, filename):
334 """
335 Check how many times the traceback is written in timeout x 2.5 seconds,
336 or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending
337 on repeat and cancel options.
338
339 Raise an error if the output doesn't match the expect format.
340 """
341 code = """
342import faulthandler
343import time
344
345def func(repeat, cancel, timeout):
346 pause = timeout * 2.5
347 a = time.time()
348 time.sleep(pause)
349 faulthandler.cancel_dump_tracebacks_later()
350 b = time.time()
351 # Check that sleep() was not interrupted
352 assert (b -a) >= pause
353
354 if cancel:
355 pause = timeout * 1.5
356 a = time.time()
357 time.sleep(pause)
358 b = time.time()
359 # Check that sleep() was not interrupted
360 assert (b -a) >= pause
361
362timeout = 0.5
363repeat = {repeat}
364cancel = {cancel}
365if {has_filename}:
366 file = open({filename}, "wb")
367else:
368 file = None
369faulthandler.dump_tracebacks_later(timeout,
370 repeat=repeat, file=file)
371func(repeat, cancel, timeout)
372if file is not None:
373 file.close()
374""".strip()
375 code = code.format(
376 filename=repr(filename),
377 has_filename=bool(filename),
378 repeat=repeat,
379 cancel=cancel,
380 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200381 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200382 trace = '\n'.join(trace)
383
384 if repeat:
385 count = 2
386 else:
387 count = 1
388 header = 'Thread 0x[0-9a-f]+:\n'
389 regex = expected_traceback(7, 30, header, count=count)
390 self.assertRegex(trace, '^%s$' % regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200391 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200392
393 @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
394 'need faulthandler.dump_tracebacks_later()')
395 def check_dump_tracebacks_later(self, repeat=False, cancel=False,
396 file=False):
397 if file:
398 with temporary_filename() as filename:
399 self._check_dump_tracebacks_later(repeat, cancel, filename)
400 else:
401 self._check_dump_tracebacks_later(repeat, cancel, None)
402
403 def test_dump_tracebacks_later(self):
404 self.check_dump_tracebacks_later()
405
406 def test_dump_tracebacks_later_repeat(self):
407 self.check_dump_tracebacks_later(repeat=True)
408
409 def test_dump_tracebacks_later_repeat_cancel(self):
410 self.check_dump_tracebacks_later(repeat=True, cancel=True)
411
412 def test_dump_tracebacks_later_file(self):
413 self.check_dump_tracebacks_later(file=True)
414
415 @unittest.skipIf(not hasattr(faulthandler, "register"),
416 "need faulthandler.register")
417 def check_register(self, filename=False, all_threads=False):
418 """
419 Register a handler displaying the traceback on a user signal. Raise the
420 signal and check the written traceback.
421
422 Raise an error if the output doesn't match the expected format.
423 """
424 code = """
425import faulthandler
426import os
427import signal
428
429def func(signum):
430 os.kill(os.getpid(), signum)
431
432signum = signal.SIGUSR1
433if {has_filename}:
434 file = open({filename}, "wb")
435else:
436 file = None
437faulthandler.register(signum, file=file, all_threads={all_threads})
438func(signum)
439if file is not None:
440 file.close()
441""".strip()
442 code = code.format(
443 filename=repr(filename),
444 has_filename=bool(filename),
445 all_threads=all_threads,
446 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200447 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200448 trace = '\n'.join(trace)
449 if all_threads:
450 regex = 'Current thread XXX:\n'
451 else:
452 regex = 'Traceback \(most recent call first\):\n'
453 regex = expected_traceback(6, 14, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200454 self.assertRegex(trace, '^%s$' % regex)
455 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200456
457 def test_register(self):
458 self.check_register()
459
460 def test_register_file(self):
461 with temporary_filename() as filename:
462 self.check_register(filename=filename)
463
464 def test_register_threads(self):
465 self.check_register(all_threads=True)
466
467
468def test_main():
469 support.run_unittest(FaultHandlerTests)
470
471if __name__ == "__main__":
472 test_main()