blob: 483c7f1a29f8e669639a8fe6c9f56ab6cabc88fb [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
Victor Stinnerde10f402011-04-08 12:57:06 +0200355 def _check_dump_tracebacks_later(self, repeat, cancel, filename, loops):
Victor Stinner024e37a2011-03-31 01:31:06 +0200356 """
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
Victor Stinnerde10f402011-04-08 12:57:06 +0200367def func(timeout, repeat, cancel, file, loops):
368 for loop in range(loops):
369 faulthandler.dump_tracebacks_later(timeout, repeat=repeat, file=file)
370 if cancel:
371 faulthandler.cancel_dump_tracebacks_later()
372 time.sleep(timeout * 2.5)
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200373 faulthandler.cancel_dump_tracebacks_later()
Victor Stinner024e37a2011-03-31 01:31:06 +0200374
Victor Stinner44378d42011-04-01 15:37:12 +0200375timeout = {timeout}
Victor Stinner024e37a2011-03-31 01:31:06 +0200376repeat = {repeat}
377cancel = {cancel}
Victor Stinnerde10f402011-04-08 12:57:06 +0200378loops = {loops}
Victor Stinner024e37a2011-03-31 01:31:06 +0200379if {has_filename}:
380 file = open({filename}, "wb")
381else:
382 file = None
Victor Stinnerde10f402011-04-08 12:57:06 +0200383func(timeout, repeat, cancel, file, loops)
Victor Stinner024e37a2011-03-31 01:31:06 +0200384if file is not None:
385 file.close()
386""".strip()
387 code = code.format(
Victor Stinnerde10f402011-04-08 12:57:06 +0200388 timeout=TIMEOUT,
Victor Stinner024e37a2011-03-31 01:31:06 +0200389 repeat=repeat,
390 cancel=cancel,
Victor Stinnerde10f402011-04-08 12:57:06 +0200391 loops=loops,
392 has_filename=bool(filename),
393 filename=repr(filename),
Victor Stinner024e37a2011-03-31 01:31:06 +0200394 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200395 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200396 trace = '\n'.join(trace)
397
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200398 if not cancel:
Victor Stinnerde10f402011-04-08 12:57:06 +0200399 count = loops
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200400 if repeat:
Victor Stinnerde10f402011-04-08 12:57:06 +0200401 count *= 2
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200402 header = 'Thread 0x[0-9a-f]+:\n'
Victor Stinnerde10f402011-04-08 12:57:06 +0200403 regex = expected_traceback(9, 20, header, count=count)
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200404 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200405 else:
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200406 self.assertEqual(trace, '')
Victor Stinner05585cb2011-03-31 13:29:56 +0200407 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200408
409 @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
410 'need faulthandler.dump_tracebacks_later()')
411 def check_dump_tracebacks_later(self, repeat=False, cancel=False,
Victor Stinnerde10f402011-04-08 12:57:06 +0200412 file=False, twice=False):
413 if twice:
414 loops = 2
415 else:
416 loops = 1
Victor Stinner024e37a2011-03-31 01:31:06 +0200417 if file:
418 with temporary_filename() as filename:
Victor Stinnerde10f402011-04-08 12:57:06 +0200419 self._check_dump_tracebacks_later(repeat, cancel,
420 filename, loops)
Victor Stinner024e37a2011-03-31 01:31:06 +0200421 else:
Victor Stinnerde10f402011-04-08 12:57:06 +0200422 self._check_dump_tracebacks_later(repeat, cancel, None, loops)
Victor Stinner024e37a2011-03-31 01:31:06 +0200423
424 def test_dump_tracebacks_later(self):
425 self.check_dump_tracebacks_later()
426
427 def test_dump_tracebacks_later_repeat(self):
428 self.check_dump_tracebacks_later(repeat=True)
429
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200430 def test_dump_tracebacks_later_cancel(self):
431 self.check_dump_tracebacks_later(cancel=True)
Victor Stinner024e37a2011-03-31 01:31:06 +0200432
433 def test_dump_tracebacks_later_file(self):
434 self.check_dump_tracebacks_later(file=True)
435
Victor Stinnerde10f402011-04-08 12:57:06 +0200436 def test_dump_tracebacks_later_twice(self):
437 self.check_dump_tracebacks_later(twice=True)
438
Victor Stinner024e37a2011-03-31 01:31:06 +0200439 @unittest.skipIf(not hasattr(faulthandler, "register"),
440 "need faulthandler.register")
Victor Stinnera01ca122011-04-01 12:56:17 +0200441 def check_register(self, filename=False, all_threads=False,
442 unregister=False):
Victor Stinner024e37a2011-03-31 01:31:06 +0200443 """
444 Register a handler displaying the traceback on a user signal. Raise the
445 signal and check the written traceback.
446
447 Raise an error if the output doesn't match the expected format.
448 """
Victor Stinnera01ca122011-04-01 12:56:17 +0200449 signum = signal.SIGUSR1
Victor Stinner024e37a2011-03-31 01:31:06 +0200450 code = """
451import faulthandler
452import os
453import signal
454
455def func(signum):
456 os.kill(os.getpid(), signum)
457
Victor Stinnera01ca122011-04-01 12:56:17 +0200458signum = {signum}
459unregister = {unregister}
Victor Stinner024e37a2011-03-31 01:31:06 +0200460if {has_filename}:
461 file = open({filename}, "wb")
462else:
463 file = None
464faulthandler.register(signum, file=file, all_threads={all_threads})
Victor Stinnera01ca122011-04-01 12:56:17 +0200465if unregister:
466 faulthandler.unregister(signum)
Victor Stinner024e37a2011-03-31 01:31:06 +0200467func(signum)
468if file is not None:
469 file.close()
470""".strip()
471 code = code.format(
472 filename=repr(filename),
473 has_filename=bool(filename),
474 all_threads=all_threads,
Victor Stinnera01ca122011-04-01 12:56:17 +0200475 signum=signum,
476 unregister=unregister,
Victor Stinner024e37a2011-03-31 01:31:06 +0200477 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200478 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200479 trace = '\n'.join(trace)
Victor Stinnera01ca122011-04-01 12:56:17 +0200480 if not unregister:
481 if all_threads:
482 regex = 'Current thread XXX:\n'
483 else:
484 regex = 'Traceback \(most recent call first\):\n'
485 regex = expected_traceback(6, 17, regex)
486 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200487 else:
Victor Stinnera01ca122011-04-01 12:56:17 +0200488 self.assertEqual(trace, '')
489 if unregister:
490 self.assertNotEqual(exitcode, 0)
491 else:
492 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200493
494 def test_register(self):
495 self.check_register()
496
Victor Stinnera01ca122011-04-01 12:56:17 +0200497 def test_unregister(self):
498 self.check_register(unregister=True)
499
Victor Stinner024e37a2011-03-31 01:31:06 +0200500 def test_register_file(self):
501 with temporary_filename() as filename:
502 self.check_register(filename=filename)
503
504 def test_register_threads(self):
505 self.check_register(all_threads=True)
506
507
508def test_main():
509 support.run_unittest(FaultHandlerTests)
510
511if __name__ == "__main__":
512 test_main()