blob: bfe662eb021a9252249aef7578e4a74594d8d327 [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
Victor Stinnerff4cd882011-04-07 11:50:25 +020011try:
12 import threading
13 HAVE_THREADS = True
14except ImportError:
15 HAVE_THREADS = False
16
Victor Stinner44378d42011-04-01 15:37:12 +020017TIMEOUT = 0.5
18
Victor Stinner024e37a2011-03-31 01:31:06 +020019try:
20 from resource import setrlimit, RLIMIT_CORE, error as resource_error
21except ImportError:
22 prepare_subprocess = None
23else:
24 def prepare_subprocess():
25 # don't create core file
26 try:
27 setrlimit(RLIMIT_CORE, (0, 0))
28 except (ValueError, resource_error):
29 pass
30
31def expected_traceback(lineno1, lineno2, header, count=1):
32 regex = header
Victor Stinner7ad24e92011-03-31 22:35:49 +020033 regex += ' File "<string>", line %s in func\n' % lineno1
34 regex += ' File "<string>", line %s in <module>' % lineno2
Victor Stinner024e37a2011-03-31 01:31:06 +020035 if count != 1:
36 regex = (regex + '\n') * (count - 1) + regex
37 return '^' + regex + '$'
38
39@contextmanager
40def temporary_filename():
41 filename = tempfile.mktemp()
42 try:
43 yield filename
44 finally:
45 support.unlink(filename)
46
47class FaultHandlerTests(unittest.TestCase):
Victor Stinner05585cb2011-03-31 13:29:56 +020048 def get_output(self, code, filename=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020049 """
50 Run the specified code in Python (in a new child process) and read the
51 output from the standard error or from a file (if filename is set).
52 Return the output lines as a list.
53
54 Strip the reference count from the standard error for Python debug
55 build, and replace "Current thread 0x00007f8d8fbd9700" by "Current
56 thread XXX".
57 """
58 options = {}
59 if prepare_subprocess:
60 options['preexec_fn'] = prepare_subprocess
61 process = script_helper.spawn_python('-c', code, **options)
62 stdout, stderr = process.communicate()
63 exitcode = process.wait()
Victor Stinner19402332011-03-31 18:15:52 +020064 output = support.strip_python_stderr(stdout)
65 output = output.decode('ascii', 'backslashreplace')
Victor Stinner024e37a2011-03-31 01:31:06 +020066 if filename:
Victor Stinner19402332011-03-31 18:15:52 +020067 self.assertEqual(output, '')
Victor Stinner024e37a2011-03-31 01:31:06 +020068 with open(filename, "rb") as fp:
69 output = fp.read()
Victor Stinner19402332011-03-31 18:15:52 +020070 output = output.decode('ascii', 'backslashreplace')
Victor Stinner024e37a2011-03-31 01:31:06 +020071 output = re.sub('Current thread 0x[0-9a-f]+',
72 'Current thread XXX',
73 output)
Victor Stinner05585cb2011-03-31 13:29:56 +020074 return output.splitlines(), exitcode
Victor Stinner024e37a2011-03-31 01:31:06 +020075
76 def check_fatal_error(self, code, line_number, name_regex,
Victor Stinnerf0480752011-03-31 11:34:08 +020077 filename=None, all_threads=False, other_regex=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020078 """
79 Check that the fault handler for fatal errors is enabled and check the
80 traceback from the child process output.
81
82 Raise an error if the output doesn't match the expected format.
83 """
84 if all_threads:
85 header = 'Current thread XXX'
86 else:
87 header = 'Traceback (most recent call first)'
88 regex = """
89^Fatal Python error: {name}
90
91{header}:
92 File "<string>", line {lineno} in <module>$
93""".strip()
94 regex = regex.format(
95 lineno=line_number,
96 name=name_regex,
97 header=re.escape(header))
Victor Stinnerf0480752011-03-31 11:34:08 +020098 if other_regex:
99 regex += '|' + other_regex
Victor Stinner05585cb2011-03-31 13:29:56 +0200100 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200101 output = '\n'.join(output)
102 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200103 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200104
105 def test_read_null(self):
106 self.check_fatal_error("""
107import faulthandler
108faulthandler.enable()
109faulthandler._read_null()
110""".strip(),
111 3,
112 '(?:Segmentation fault|Bus error)')
113
114 def test_sigsegv(self):
115 self.check_fatal_error("""
116import faulthandler
117faulthandler.enable()
118faulthandler._sigsegv()
119""".strip(),
120 3,
121 'Segmentation fault')
122
Victor Stinnerd727e232011-04-01 12:13:55 +0200123 def test_sigabrt(self):
124 self.check_fatal_error("""
125import faulthandler
126faulthandler.enable()
127faulthandler._sigabrt()
128""".strip(),
129 3,
130 'Aborted')
131
Victor Stinner024e37a2011-03-31 01:31:06 +0200132 @unittest.skipIf(sys.platform == 'win32',
133 "SIGFPE cannot be caught on Windows")
134 def test_sigfpe(self):
135 self.check_fatal_error("""
136import faulthandler
137faulthandler.enable()
138faulthandler._sigfpe()
139""".strip(),
140 3,
141 'Floating point exception')
142
143 @unittest.skipIf(not hasattr(faulthandler, '_sigbus'),
144 "need faulthandler._sigbus()")
145 def test_sigbus(self):
146 self.check_fatal_error("""
147import faulthandler
148faulthandler.enable()
149faulthandler._sigbus()
150""".strip(),
151 3,
152 'Bus error')
153
154 @unittest.skipIf(not hasattr(faulthandler, '_sigill'),
155 "need faulthandler._sigill()")
156 def test_sigill(self):
157 self.check_fatal_error("""
158import faulthandler
159faulthandler.enable()
160faulthandler._sigill()
161""".strip(),
162 3,
163 'Illegal instruction')
164
165 def test_fatal_error(self):
166 self.check_fatal_error("""
167import faulthandler
168faulthandler._fatal_error(b'xyz')
169""".strip(),
170 2,
171 'xyz')
172
Victor Stinner024e37a2011-03-31 01:31:06 +0200173 @unittest.skipIf(not hasattr(faulthandler, '_stack_overflow'),
174 'need faulthandler._stack_overflow()')
175 def test_stack_overflow(self):
176 self.check_fatal_error("""
177import faulthandler
178faulthandler.enable()
179faulthandler._stack_overflow()
180""".strip(),
181 3,
Victor Stinnerf0480752011-03-31 11:34:08 +0200182 '(?:Segmentation fault|Bus error)',
183 other_regex='unable to raise a stack overflow')
Victor Stinner024e37a2011-03-31 01:31:06 +0200184
185 def test_gil_released(self):
186 self.check_fatal_error("""
187import faulthandler
188faulthandler.enable()
189faulthandler._read_null(True)
190""".strip(),
191 3,
192 '(?:Segmentation fault|Bus error)')
193
194 def test_enable_file(self):
195 with temporary_filename() as filename:
196 self.check_fatal_error("""
197import faulthandler
198output = open({filename}, 'wb')
199faulthandler.enable(output)
Victor Stinner44378d42011-04-01 15:37:12 +0200200faulthandler._read_null()
Victor Stinner024e37a2011-03-31 01:31:06 +0200201""".strip().format(filename=repr(filename)),
202 4,
203 '(?:Segmentation fault|Bus error)',
204 filename=filename)
205
206 def test_enable_threads(self):
207 self.check_fatal_error("""
208import faulthandler
209faulthandler.enable(all_threads=True)
Victor Stinner44378d42011-04-01 15:37:12 +0200210faulthandler._read_null()
Victor Stinner024e37a2011-03-31 01:31:06 +0200211""".strip(),
212 3,
213 '(?:Segmentation fault|Bus error)',
214 all_threads=True)
215
216 def test_disable(self):
217 code = """
218import faulthandler
219faulthandler.enable()
220faulthandler.disable()
221faulthandler._read_null()
222""".strip()
223 not_expected = 'Fatal Python error'
Victor Stinner05585cb2011-03-31 13:29:56 +0200224 stderr, exitcode = self.get_output(code)
Victor Stinner024e37a2011-03-31 01:31:06 +0200225 stder = '\n'.join(stderr)
226 self.assertTrue(not_expected not in stderr,
227 "%r is present in %r" % (not_expected, stderr))
Victor Stinner05585cb2011-03-31 13:29:56 +0200228 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200229
230 def test_is_enabled(self):
231 was_enabled = faulthandler.is_enabled()
232 try:
233 faulthandler.enable()
234 self.assertTrue(faulthandler.is_enabled())
235 faulthandler.disable()
236 self.assertFalse(faulthandler.is_enabled())
237 finally:
238 if was_enabled:
239 faulthandler.enable()
240 else:
241 faulthandler.disable()
242
243 def check_dump_traceback(self, filename):
244 """
245 Explicitly call dump_traceback() function and check its output.
246 Raise an error if the output doesn't match the expected format.
247 """
248 code = """
249import faulthandler
250
251def funcB():
252 if {has_filename}:
253 with open({filename}, "wb") as fp:
254 faulthandler.dump_traceback(fp)
255 else:
256 faulthandler.dump_traceback()
257
258def funcA():
259 funcB()
260
261funcA()
262""".strip()
263 code = code.format(
264 filename=repr(filename),
265 has_filename=bool(filename),
266 )
267 if filename:
268 lineno = 6
269 else:
270 lineno = 8
271 expected = [
272 'Traceback (most recent call first):',
273 ' File "<string>", line %s in funcB' % lineno,
274 ' File "<string>", line 11 in funcA',
275 ' File "<string>", line 13 in <module>'
276 ]
Victor Stinner05585cb2011-03-31 13:29:56 +0200277 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200278 self.assertEqual(trace, expected)
Victor Stinner05585cb2011-03-31 13:29:56 +0200279 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200280
281 def test_dump_traceback(self):
282 self.check_dump_traceback(None)
Victor Stinner19402332011-03-31 18:15:52 +0200283
284 def test_dump_traceback_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200285 with temporary_filename() as filename:
286 self.check_dump_traceback(filename)
287
Victor Stinnerff4cd882011-04-07 11:50:25 +0200288 @unittest.skipIf(not HAVE_THREADS, 'need threads')
Victor Stinner024e37a2011-03-31 01:31:06 +0200289 def check_dump_traceback_threads(self, filename):
290 """
291 Call explicitly dump_traceback(all_threads=True) and check the output.
292 Raise an error if the output doesn't match the expected format.
293 """
294 code = """
295import faulthandler
296from threading import Thread, Event
297import time
298
299def dump():
300 if {filename}:
301 with open({filename}, "wb") as fp:
302 faulthandler.dump_traceback(fp, all_threads=True)
303 else:
304 faulthandler.dump_traceback(all_threads=True)
305
306class Waiter(Thread):
307 # avoid blocking if the main thread raises an exception.
308 daemon = True
309
310 def __init__(self):
311 Thread.__init__(self)
312 self.running = Event()
313 self.stop = Event()
314
315 def run(self):
316 self.running.set()
317 self.stop.wait()
318
319waiter = Waiter()
320waiter.start()
321waiter.running.wait()
322dump()
323waiter.stop.set()
324waiter.join()
325""".strip()
326 code = code.format(filename=repr(filename))
Victor Stinner05585cb2011-03-31 13:29:56 +0200327 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200328 output = '\n'.join(output)
329 if filename:
330 lineno = 8
331 else:
332 lineno = 10
333 regex = """
334^Thread 0x[0-9a-f]+:
Victor Stinner1b3241f2011-04-03 18:41:22 +0200335(?: File ".*threading.py", line [0-9]+ in [_a-z]+
336){{1,3}} File "<string>", line 23 in run
Victor Stinner024e37a2011-03-31 01:31:06 +0200337 File ".*threading.py", line [0-9]+ in _bootstrap_inner
338 File ".*threading.py", line [0-9]+ in _bootstrap
339
340Current thread XXX:
341 File "<string>", line {lineno} in dump
342 File "<string>", line 28 in <module>$
343""".strip()
344 regex = regex.format(lineno=lineno)
345 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200346 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200347
348 def test_dump_traceback_threads(self):
349 self.check_dump_traceback_threads(None)
Victor Stinner19402332011-03-31 18:15:52 +0200350
351 def test_dump_traceback_threads_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200352 with temporary_filename() as filename:
353 self.check_dump_traceback_threads(filename)
354
355 def _check_dump_tracebacks_later(self, repeat, cancel, filename):
356 """
357 Check how many times the traceback is written in timeout x 2.5 seconds,
358 or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending
359 on repeat and cancel options.
360
361 Raise an error if the output doesn't match the expect format.
362 """
363 code = """
364import faulthandler
365import time
366
367def func(repeat, cancel, timeout):
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200368 if cancel:
369 faulthandler.cancel_dump_tracebacks_later()
Victor Stinner9bdb43e2011-04-04 23:42:30 +0200370 time.sleep(timeout * 2.5)
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200371 faulthandler.cancel_dump_tracebacks_later()
Victor Stinner024e37a2011-03-31 01:31:06 +0200372
Victor Stinner44378d42011-04-01 15:37:12 +0200373timeout = {timeout}
Victor Stinner024e37a2011-03-31 01:31:06 +0200374repeat = {repeat}
375cancel = {cancel}
376if {has_filename}:
377 file = open({filename}, "wb")
378else:
379 file = None
380faulthandler.dump_tracebacks_later(timeout,
381 repeat=repeat, file=file)
382func(repeat, cancel, timeout)
383if file is not None:
384 file.close()
385""".strip()
386 code = code.format(
387 filename=repr(filename),
388 has_filename=bool(filename),
389 repeat=repeat,
390 cancel=cancel,
Victor Stinner44378d42011-04-01 15:37:12 +0200391 timeout=TIMEOUT,
Victor Stinner024e37a2011-03-31 01:31:06 +0200392 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200393 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200394 trace = '\n'.join(trace)
395
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200396 if not cancel:
397 if repeat:
398 count = 2
399 else:
400 count = 1
401 header = 'Thread 0x[0-9a-f]+:\n'
Victor Stinner9bdb43e2011-04-04 23:42:30 +0200402 regex = expected_traceback(7, 19, header, count=count)
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200403 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200404 else:
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200405 self.assertEqual(trace, '')
Victor Stinner05585cb2011-03-31 13:29:56 +0200406 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200407
408 @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
409 'need faulthandler.dump_tracebacks_later()')
410 def check_dump_tracebacks_later(self, repeat=False, cancel=False,
411 file=False):
412 if file:
413 with temporary_filename() as filename:
414 self._check_dump_tracebacks_later(repeat, cancel, filename)
415 else:
416 self._check_dump_tracebacks_later(repeat, cancel, None)
417
418 def test_dump_tracebacks_later(self):
419 self.check_dump_tracebacks_later()
420
421 def test_dump_tracebacks_later_repeat(self):
422 self.check_dump_tracebacks_later(repeat=True)
423
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200424 def test_dump_tracebacks_later_cancel(self):
425 self.check_dump_tracebacks_later(cancel=True)
Victor Stinner024e37a2011-03-31 01:31:06 +0200426
427 def test_dump_tracebacks_later_file(self):
428 self.check_dump_tracebacks_later(file=True)
429
430 @unittest.skipIf(not hasattr(faulthandler, "register"),
431 "need faulthandler.register")
Victor Stinnera01ca122011-04-01 12:56:17 +0200432 def check_register(self, filename=False, all_threads=False,
433 unregister=False):
Victor Stinner024e37a2011-03-31 01:31:06 +0200434 """
435 Register a handler displaying the traceback on a user signal. Raise the
436 signal and check the written traceback.
437
438 Raise an error if the output doesn't match the expected format.
439 """
Victor Stinnera01ca122011-04-01 12:56:17 +0200440 signum = signal.SIGUSR1
Victor Stinner024e37a2011-03-31 01:31:06 +0200441 code = """
442import faulthandler
443import os
444import signal
445
446def func(signum):
447 os.kill(os.getpid(), signum)
448
Victor Stinnera01ca122011-04-01 12:56:17 +0200449signum = {signum}
450unregister = {unregister}
Victor Stinner024e37a2011-03-31 01:31:06 +0200451if {has_filename}:
452 file = open({filename}, "wb")
453else:
454 file = None
455faulthandler.register(signum, file=file, all_threads={all_threads})
Victor Stinnera01ca122011-04-01 12:56:17 +0200456if unregister:
457 faulthandler.unregister(signum)
Victor Stinner024e37a2011-03-31 01:31:06 +0200458func(signum)
459if file is not None:
460 file.close()
461""".strip()
462 code = code.format(
463 filename=repr(filename),
464 has_filename=bool(filename),
465 all_threads=all_threads,
Victor Stinnera01ca122011-04-01 12:56:17 +0200466 signum=signum,
467 unregister=unregister,
Victor Stinner024e37a2011-03-31 01:31:06 +0200468 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200469 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200470 trace = '\n'.join(trace)
Victor Stinnera01ca122011-04-01 12:56:17 +0200471 if not unregister:
472 if all_threads:
473 regex = 'Current thread XXX:\n'
474 else:
475 regex = 'Traceback \(most recent call first\):\n'
476 regex = expected_traceback(6, 17, regex)
477 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200478 else:
Victor Stinnera01ca122011-04-01 12:56:17 +0200479 self.assertEqual(trace, '')
480 if unregister:
481 self.assertNotEqual(exitcode, 0)
482 else:
483 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200484
485 def test_register(self):
486 self.check_register()
487
Victor Stinnera01ca122011-04-01 12:56:17 +0200488 def test_unregister(self):
489 self.check_register(unregister=True)
490
Victor Stinner024e37a2011-03-31 01:31:06 +0200491 def test_register_file(self):
492 with temporary_filename() as filename:
493 self.check_register(filename=filename)
494
495 def test_register_threads(self):
496 self.check_register(all_threads=True)
497
498
499def test_main():
500 support.run_unittest(FaultHandlerTests)
501
502if __name__ == "__main__":
503 test_main()