blob: 0ddfc7f66fa2564262775b76d95881a399431eca [file] [log] [blame]
Victor Stinner024e37a2011-03-31 01:31:06 +02001from contextlib import contextmanager
Victor Stinnerc790a532011-04-08 13:39:59 +02002import datetime
Victor Stinner024e37a2011-03-31 01:31:06 +02003import faulthandler
4import re
5import signal
6import subprocess
7import sys
8from test import support, script_helper
9import tempfile
10import unittest
11
Victor Stinnerff4cd882011-04-07 11:50:25 +020012try:
13 import threading
14 HAVE_THREADS = True
15except ImportError:
16 HAVE_THREADS = False
17
Victor Stinner44378d42011-04-01 15:37:12 +020018TIMEOUT = 0.5
19
Victor Stinner024e37a2011-03-31 01:31:06 +020020try:
21 from resource import setrlimit, RLIMIT_CORE, error as resource_error
22except ImportError:
23 prepare_subprocess = None
24else:
25 def prepare_subprocess():
26 # don't create core file
27 try:
28 setrlimit(RLIMIT_CORE, (0, 0))
29 except (ValueError, resource_error):
30 pass
31
Victor Stinner301f3f02011-06-01 13:49:12 +020032def expected_traceback(lineno1, lineno2, header, min_count=1):
Victor Stinner024e37a2011-03-31 01:31:06 +020033 regex = header
Victor Stinner7ad24e92011-03-31 22:35:49 +020034 regex += ' File "<string>", line %s in func\n' % lineno1
35 regex += ' File "<string>", line %s in <module>' % lineno2
Victor Stinner301f3f02011-06-01 13:49:12 +020036 if 1 < min_count:
37 return '^' + (regex + '\n') * (min_count - 1) + regex
38 else:
39 return '^' + regex + '$'
Victor Stinner024e37a2011-03-31 01:31:06 +020040
41@contextmanager
42def temporary_filename():
43 filename = tempfile.mktemp()
44 try:
45 yield filename
46 finally:
47 support.unlink(filename)
48
49class FaultHandlerTests(unittest.TestCase):
Victor Stinner05585cb2011-03-31 13:29:56 +020050 def get_output(self, code, filename=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020051 """
52 Run the specified code in Python (in a new child process) and read the
53 output from the standard error or from a file (if filename is set).
54 Return the output lines as a list.
55
56 Strip the reference count from the standard error for Python debug
57 build, and replace "Current thread 0x00007f8d8fbd9700" by "Current
58 thread XXX".
59 """
60 options = {}
61 if prepare_subprocess:
62 options['preexec_fn'] = prepare_subprocess
63 process = script_helper.spawn_python('-c', code, **options)
64 stdout, stderr = process.communicate()
65 exitcode = process.wait()
Victor Stinner19402332011-03-31 18:15:52 +020066 output = support.strip_python_stderr(stdout)
67 output = output.decode('ascii', 'backslashreplace')
Victor Stinner024e37a2011-03-31 01:31:06 +020068 if filename:
Victor Stinner19402332011-03-31 18:15:52 +020069 self.assertEqual(output, '')
Victor Stinner024e37a2011-03-31 01:31:06 +020070 with open(filename, "rb") as fp:
71 output = fp.read()
Victor Stinner19402332011-03-31 18:15:52 +020072 output = output.decode('ascii', 'backslashreplace')
Victor Stinner024e37a2011-03-31 01:31:06 +020073 output = re.sub('Current thread 0x[0-9a-f]+',
74 'Current thread XXX',
75 output)
Victor Stinner05585cb2011-03-31 13:29:56 +020076 return output.splitlines(), exitcode
Victor Stinner024e37a2011-03-31 01:31:06 +020077
78 def check_fatal_error(self, code, line_number, name_regex,
Victor Stinner7bba62f2011-05-07 12:43:00 +020079 filename=None, all_threads=True, other_regex=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020080 """
81 Check that the fault handler for fatal errors is enabled and check the
82 traceback from the child process output.
83
84 Raise an error if the output doesn't match the expected format.
85 """
86 if all_threads:
87 header = 'Current thread XXX'
88 else:
89 header = 'Traceback (most recent call first)'
90 regex = """
91^Fatal Python error: {name}
92
93{header}:
94 File "<string>", line {lineno} in <module>$
95""".strip()
96 regex = regex.format(
97 lineno=line_number,
98 name=name_regex,
99 header=re.escape(header))
Victor Stinnerf0480752011-03-31 11:34:08 +0200100 if other_regex:
101 regex += '|' + other_regex
Victor Stinner05585cb2011-03-31 13:29:56 +0200102 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200103 output = '\n'.join(output)
104 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200105 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200106
107 def test_read_null(self):
108 self.check_fatal_error("""
109import faulthandler
110faulthandler.enable()
111faulthandler._read_null()
112""".strip(),
113 3,
114 '(?:Segmentation fault|Bus error)')
115
116 def test_sigsegv(self):
117 self.check_fatal_error("""
118import faulthandler
119faulthandler.enable()
120faulthandler._sigsegv()
121""".strip(),
122 3,
123 'Segmentation fault')
124
Victor Stinnerd727e232011-04-01 12:13:55 +0200125 def test_sigabrt(self):
126 self.check_fatal_error("""
127import faulthandler
128faulthandler.enable()
129faulthandler._sigabrt()
130""".strip(),
131 3,
132 'Aborted')
133
Victor Stinner024e37a2011-03-31 01:31:06 +0200134 @unittest.skipIf(sys.platform == 'win32',
135 "SIGFPE cannot be caught on Windows")
136 def test_sigfpe(self):
137 self.check_fatal_error("""
138import faulthandler
139faulthandler.enable()
140faulthandler._sigfpe()
141""".strip(),
142 3,
143 'Floating point exception')
144
145 @unittest.skipIf(not hasattr(faulthandler, '_sigbus'),
146 "need faulthandler._sigbus()")
147 def test_sigbus(self):
148 self.check_fatal_error("""
149import faulthandler
150faulthandler.enable()
151faulthandler._sigbus()
152""".strip(),
153 3,
154 'Bus error')
155
156 @unittest.skipIf(not hasattr(faulthandler, '_sigill'),
157 "need faulthandler._sigill()")
158 def test_sigill(self):
159 self.check_fatal_error("""
160import faulthandler
161faulthandler.enable()
162faulthandler._sigill()
163""".strip(),
164 3,
165 'Illegal instruction')
166
167 def test_fatal_error(self):
168 self.check_fatal_error("""
169import faulthandler
170faulthandler._fatal_error(b'xyz')
171""".strip(),
172 2,
173 'xyz')
174
Victor Stinner024e37a2011-03-31 01:31:06 +0200175 @unittest.skipIf(not hasattr(faulthandler, '_stack_overflow'),
176 'need faulthandler._stack_overflow()')
177 def test_stack_overflow(self):
178 self.check_fatal_error("""
179import faulthandler
180faulthandler.enable()
181faulthandler._stack_overflow()
182""".strip(),
183 3,
Victor Stinnerf0480752011-03-31 11:34:08 +0200184 '(?:Segmentation fault|Bus error)',
185 other_regex='unable to raise a stack overflow')
Victor Stinner024e37a2011-03-31 01:31:06 +0200186
187 def test_gil_released(self):
188 self.check_fatal_error("""
189import faulthandler
190faulthandler.enable()
191faulthandler._read_null(True)
192""".strip(),
193 3,
194 '(?:Segmentation fault|Bus error)')
195
196 def test_enable_file(self):
197 with temporary_filename() as filename:
198 self.check_fatal_error("""
199import faulthandler
200output = open({filename}, 'wb')
201faulthandler.enable(output)
Victor Stinner44378d42011-04-01 15:37:12 +0200202faulthandler._read_null()
Victor Stinner024e37a2011-03-31 01:31:06 +0200203""".strip().format(filename=repr(filename)),
204 4,
205 '(?:Segmentation fault|Bus error)',
206 filename=filename)
207
Victor Stinner7bba62f2011-05-07 12:43:00 +0200208 def test_enable_single_thread(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200209 self.check_fatal_error("""
210import faulthandler
Victor Stinner7bba62f2011-05-07 12:43:00 +0200211faulthandler.enable(all_threads=False)
Victor Stinner44378d42011-04-01 15:37:12 +0200212faulthandler._read_null()
Victor Stinner024e37a2011-03-31 01:31:06 +0200213""".strip(),
214 3,
215 '(?:Segmentation fault|Bus error)',
Victor Stinner7bba62f2011-05-07 12:43:00 +0200216 all_threads=False)
Victor Stinner024e37a2011-03-31 01:31:06 +0200217
218 def test_disable(self):
219 code = """
220import faulthandler
221faulthandler.enable()
222faulthandler.disable()
223faulthandler._read_null()
224""".strip()
225 not_expected = 'Fatal Python error'
Victor Stinner05585cb2011-03-31 13:29:56 +0200226 stderr, exitcode = self.get_output(code)
Victor Stinner024e37a2011-03-31 01:31:06 +0200227 stder = '\n'.join(stderr)
228 self.assertTrue(not_expected not in stderr,
229 "%r is present in %r" % (not_expected, stderr))
Victor Stinner05585cb2011-03-31 13:29:56 +0200230 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200231
232 def test_is_enabled(self):
233 was_enabled = faulthandler.is_enabled()
234 try:
235 faulthandler.enable()
236 self.assertTrue(faulthandler.is_enabled())
237 faulthandler.disable()
238 self.assertFalse(faulthandler.is_enabled())
239 finally:
240 if was_enabled:
241 faulthandler.enable()
242 else:
243 faulthandler.disable()
244
245 def check_dump_traceback(self, filename):
246 """
247 Explicitly call dump_traceback() function and check its output.
248 Raise an error if the output doesn't match the expected format.
249 """
250 code = """
251import faulthandler
252
253def funcB():
254 if {has_filename}:
255 with open({filename}, "wb") as fp:
Victor Stinner7bba62f2011-05-07 12:43:00 +0200256 faulthandler.dump_traceback(fp, all_threads=False)
Victor Stinner024e37a2011-03-31 01:31:06 +0200257 else:
Victor Stinner7bba62f2011-05-07 12:43:00 +0200258 faulthandler.dump_traceback(all_threads=False)
Victor Stinner024e37a2011-03-31 01:31:06 +0200259
260def funcA():
261 funcB()
262
263funcA()
264""".strip()
265 code = code.format(
266 filename=repr(filename),
267 has_filename=bool(filename),
268 )
269 if filename:
270 lineno = 6
271 else:
272 lineno = 8
273 expected = [
274 'Traceback (most recent call first):',
275 ' File "<string>", line %s in funcB' % lineno,
276 ' File "<string>", line 11 in funcA',
277 ' File "<string>", line 13 in <module>'
278 ]
Victor Stinner05585cb2011-03-31 13:29:56 +0200279 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200280 self.assertEqual(trace, expected)
Victor Stinner05585cb2011-03-31 13:29:56 +0200281 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200282
283 def test_dump_traceback(self):
284 self.check_dump_traceback(None)
Victor Stinner19402332011-03-31 18:15:52 +0200285
286 def test_dump_traceback_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200287 with temporary_filename() as filename:
288 self.check_dump_traceback(filename)
289
Victor Stinnerff4cd882011-04-07 11:50:25 +0200290 @unittest.skipIf(not HAVE_THREADS, 'need threads')
Victor Stinner024e37a2011-03-31 01:31:06 +0200291 def check_dump_traceback_threads(self, filename):
292 """
293 Call explicitly dump_traceback(all_threads=True) and check the output.
294 Raise an error if the output doesn't match the expected format.
295 """
296 code = """
297import faulthandler
298from threading import Thread, Event
299import time
300
301def dump():
302 if {filename}:
303 with open({filename}, "wb") as fp:
304 faulthandler.dump_traceback(fp, all_threads=True)
305 else:
306 faulthandler.dump_traceback(all_threads=True)
307
308class Waiter(Thread):
309 # avoid blocking if the main thread raises an exception.
310 daemon = True
311
312 def __init__(self):
313 Thread.__init__(self)
314 self.running = Event()
315 self.stop = Event()
316
317 def run(self):
318 self.running.set()
319 self.stop.wait()
320
321waiter = Waiter()
322waiter.start()
323waiter.running.wait()
324dump()
325waiter.stop.set()
326waiter.join()
327""".strip()
328 code = code.format(filename=repr(filename))
Victor Stinner05585cb2011-03-31 13:29:56 +0200329 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200330 output = '\n'.join(output)
331 if filename:
332 lineno = 8
333 else:
334 lineno = 10
335 regex = """
336^Thread 0x[0-9a-f]+:
Victor Stinner1b3241f2011-04-03 18:41:22 +0200337(?: File ".*threading.py", line [0-9]+ in [_a-z]+
338){{1,3}} File "<string>", line 23 in run
Victor Stinner024e37a2011-03-31 01:31:06 +0200339 File ".*threading.py", line [0-9]+ in _bootstrap_inner
340 File ".*threading.py", line [0-9]+ in _bootstrap
341
342Current thread XXX:
343 File "<string>", line {lineno} in dump
344 File "<string>", line 28 in <module>$
345""".strip()
346 regex = regex.format(lineno=lineno)
347 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200348 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200349
350 def test_dump_traceback_threads(self):
351 self.check_dump_traceback_threads(None)
Victor Stinner19402332011-03-31 18:15:52 +0200352
353 def test_dump_traceback_threads_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200354 with temporary_filename() as filename:
355 self.check_dump_traceback_threads(filename)
356
Victor Stinnerde10f402011-04-08 12:57:06 +0200357 def _check_dump_tracebacks_later(self, repeat, cancel, filename, loops):
Victor Stinner024e37a2011-03-31 01:31:06 +0200358 """
359 Check how many times the traceback is written in timeout x 2.5 seconds,
360 or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending
361 on repeat and cancel options.
362
363 Raise an error if the output doesn't match the expect format.
364 """
Victor Stinnerc790a532011-04-08 13:39:59 +0200365 timeout_str = str(datetime.timedelta(seconds=TIMEOUT))
Victor Stinner024e37a2011-03-31 01:31:06 +0200366 code = """
367import faulthandler
368import time
369
Victor Stinnerde10f402011-04-08 12:57:06 +0200370def func(timeout, repeat, cancel, file, loops):
371 for loop in range(loops):
372 faulthandler.dump_tracebacks_later(timeout, repeat=repeat, file=file)
373 if cancel:
374 faulthandler.cancel_dump_tracebacks_later()
Victor Stinner301f3f02011-06-01 13:49:12 +0200375 time.sleep(timeout * 5)
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200376 faulthandler.cancel_dump_tracebacks_later()
Victor Stinner024e37a2011-03-31 01:31:06 +0200377
Victor Stinner44378d42011-04-01 15:37:12 +0200378timeout = {timeout}
Victor Stinner024e37a2011-03-31 01:31:06 +0200379repeat = {repeat}
380cancel = {cancel}
Victor Stinnerde10f402011-04-08 12:57:06 +0200381loops = {loops}
Victor Stinner024e37a2011-03-31 01:31:06 +0200382if {has_filename}:
383 file = open({filename}, "wb")
384else:
385 file = None
Victor Stinnerde10f402011-04-08 12:57:06 +0200386func(timeout, repeat, cancel, file, loops)
Victor Stinner024e37a2011-03-31 01:31:06 +0200387if file is not None:
388 file.close()
389""".strip()
390 code = code.format(
Victor Stinnerde10f402011-04-08 12:57:06 +0200391 timeout=TIMEOUT,
Victor Stinner024e37a2011-03-31 01:31:06 +0200392 repeat=repeat,
393 cancel=cancel,
Victor Stinnerde10f402011-04-08 12:57:06 +0200394 loops=loops,
395 has_filename=bool(filename),
396 filename=repr(filename),
Victor Stinner024e37a2011-03-31 01:31:06 +0200397 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200398 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200399 trace = '\n'.join(trace)
400
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200401 if not cancel:
Victor Stinnerde10f402011-04-08 12:57:06 +0200402 count = loops
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200403 if repeat:
Victor Stinnerde10f402011-04-08 12:57:06 +0200404 count *= 2
Victor Stinnerc790a532011-04-08 13:39:59 +0200405 header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+:\n' % timeout_str
Victor Stinner301f3f02011-06-01 13:49:12 +0200406 regex = expected_traceback(9, 20, header, min_count=count)
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200407 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200408 else:
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200409 self.assertEqual(trace, '')
Victor Stinner05585cb2011-03-31 13:29:56 +0200410 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200411
412 @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
413 'need faulthandler.dump_tracebacks_later()')
414 def check_dump_tracebacks_later(self, repeat=False, cancel=False,
Victor Stinnerde10f402011-04-08 12:57:06 +0200415 file=False, twice=False):
416 if twice:
417 loops = 2
418 else:
419 loops = 1
Victor Stinner024e37a2011-03-31 01:31:06 +0200420 if file:
421 with temporary_filename() as filename:
Victor Stinnerde10f402011-04-08 12:57:06 +0200422 self._check_dump_tracebacks_later(repeat, cancel,
423 filename, loops)
Victor Stinner024e37a2011-03-31 01:31:06 +0200424 else:
Victor Stinnerde10f402011-04-08 12:57:06 +0200425 self._check_dump_tracebacks_later(repeat, cancel, None, loops)
Victor Stinner024e37a2011-03-31 01:31:06 +0200426
427 def test_dump_tracebacks_later(self):
428 self.check_dump_tracebacks_later()
429
430 def test_dump_tracebacks_later_repeat(self):
431 self.check_dump_tracebacks_later(repeat=True)
432
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200433 def test_dump_tracebacks_later_cancel(self):
434 self.check_dump_tracebacks_later(cancel=True)
Victor Stinner024e37a2011-03-31 01:31:06 +0200435
436 def test_dump_tracebacks_later_file(self):
437 self.check_dump_tracebacks_later(file=True)
438
Victor Stinnerde10f402011-04-08 12:57:06 +0200439 def test_dump_tracebacks_later_twice(self):
440 self.check_dump_tracebacks_later(twice=True)
441
Victor Stinner024e37a2011-03-31 01:31:06 +0200442 @unittest.skipIf(not hasattr(faulthandler, "register"),
443 "need faulthandler.register")
Victor Stinnera01ca122011-04-01 12:56:17 +0200444 def check_register(self, filename=False, all_threads=False,
445 unregister=False):
Victor Stinner024e37a2011-03-31 01:31:06 +0200446 """
447 Register a handler displaying the traceback on a user signal. Raise the
448 signal and check the written traceback.
449
450 Raise an error if the output doesn't match the expected format.
451 """
Victor Stinnera01ca122011-04-01 12:56:17 +0200452 signum = signal.SIGUSR1
Victor Stinner024e37a2011-03-31 01:31:06 +0200453 code = """
454import faulthandler
455import os
456import signal
457
458def func(signum):
459 os.kill(os.getpid(), signum)
460
Victor Stinnera01ca122011-04-01 12:56:17 +0200461signum = {signum}
462unregister = {unregister}
Victor Stinner024e37a2011-03-31 01:31:06 +0200463if {has_filename}:
464 file = open({filename}, "wb")
465else:
466 file = None
467faulthandler.register(signum, file=file, all_threads={all_threads})
Victor Stinnera01ca122011-04-01 12:56:17 +0200468if unregister:
469 faulthandler.unregister(signum)
Victor Stinner024e37a2011-03-31 01:31:06 +0200470func(signum)
471if file is not None:
472 file.close()
473""".strip()
474 code = code.format(
475 filename=repr(filename),
476 has_filename=bool(filename),
477 all_threads=all_threads,
Victor Stinnera01ca122011-04-01 12:56:17 +0200478 signum=signum,
479 unregister=unregister,
Victor Stinner024e37a2011-03-31 01:31:06 +0200480 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200481 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200482 trace = '\n'.join(trace)
Victor Stinnera01ca122011-04-01 12:56:17 +0200483 if not unregister:
484 if all_threads:
485 regex = 'Current thread XXX:\n'
486 else:
487 regex = 'Traceback \(most recent call first\):\n'
488 regex = expected_traceback(6, 17, regex)
489 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200490 else:
Victor Stinnera01ca122011-04-01 12:56:17 +0200491 self.assertEqual(trace, '')
492 if unregister:
493 self.assertNotEqual(exitcode, 0)
494 else:
495 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200496
497 def test_register(self):
498 self.check_register()
499
Victor Stinnera01ca122011-04-01 12:56:17 +0200500 def test_unregister(self):
501 self.check_register(unregister=True)
502
Victor Stinner024e37a2011-03-31 01:31:06 +0200503 def test_register_file(self):
504 with temporary_filename() as filename:
505 self.check_register(filename=filename)
506
507 def test_register_threads(self):
508 self.check_register(all_threads=True)
509
510
511def test_main():
512 support.run_unittest(FaultHandlerTests)
513
514if __name__ == "__main__":
515 test_main()