blob: dbc19178a1c5fac3bc023c2ea0c6aea0ca1105b2 [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
32def expected_traceback(lineno1, lineno2, header, count=1):
33 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 Stinner024e37a2011-03-31 01:31:06 +020036 if count != 1:
37 regex = (regex + '\n') * (count - 1) + regex
38 return '^' + regex + '$'
39
40@contextmanager
41def temporary_filename():
42 filename = tempfile.mktemp()
43 try:
44 yield filename
45 finally:
46 support.unlink(filename)
47
48class FaultHandlerTests(unittest.TestCase):
Victor Stinner05585cb2011-03-31 13:29:56 +020049 def get_output(self, code, filename=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020050 """
51 Run the specified code in Python (in a new child process) and read the
52 output from the standard error or from a file (if filename is set).
53 Return the output lines as a list.
54
55 Strip the reference count from the standard error for Python debug
56 build, and replace "Current thread 0x00007f8d8fbd9700" by "Current
57 thread XXX".
58 """
59 options = {}
60 if prepare_subprocess:
61 options['preexec_fn'] = prepare_subprocess
62 process = script_helper.spawn_python('-c', code, **options)
63 stdout, stderr = process.communicate()
64 exitcode = process.wait()
Victor Stinner19402332011-03-31 18:15:52 +020065 output = support.strip_python_stderr(stdout)
66 output = output.decode('ascii', 'backslashreplace')
Victor Stinner024e37a2011-03-31 01:31:06 +020067 if filename:
Victor Stinner19402332011-03-31 18:15:52 +020068 self.assertEqual(output, '')
Victor Stinner024e37a2011-03-31 01:31:06 +020069 with open(filename, "rb") as fp:
70 output = fp.read()
Victor Stinner19402332011-03-31 18:15:52 +020071 output = output.decode('ascii', 'backslashreplace')
Victor Stinner024e37a2011-03-31 01:31:06 +020072 output = re.sub('Current thread 0x[0-9a-f]+',
73 'Current thread XXX',
74 output)
Victor Stinner05585cb2011-03-31 13:29:56 +020075 return output.splitlines(), exitcode
Victor Stinner024e37a2011-03-31 01:31:06 +020076
77 def check_fatal_error(self, code, line_number, name_regex,
Victor Stinner7bba62f2011-05-07 12:43:00 +020078 filename=None, all_threads=True, other_regex=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020079 """
80 Check that the fault handler for fatal errors is enabled and check the
81 traceback from the child process output.
82
83 Raise an error if the output doesn't match the expected format.
84 """
85 if all_threads:
86 header = 'Current thread XXX'
87 else:
88 header = 'Traceback (most recent call first)'
89 regex = """
90^Fatal Python error: {name}
91
92{header}:
93 File "<string>", line {lineno} in <module>$
94""".strip()
95 regex = regex.format(
96 lineno=line_number,
97 name=name_regex,
98 header=re.escape(header))
Victor Stinnerf0480752011-03-31 11:34:08 +020099 if other_regex:
100 regex += '|' + other_regex
Victor Stinner05585cb2011-03-31 13:29:56 +0200101 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200102 output = '\n'.join(output)
103 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200104 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200105
106 def test_read_null(self):
107 self.check_fatal_error("""
108import faulthandler
109faulthandler.enable()
110faulthandler._read_null()
111""".strip(),
112 3,
113 '(?:Segmentation fault|Bus error)')
114
115 def test_sigsegv(self):
116 self.check_fatal_error("""
117import faulthandler
118faulthandler.enable()
119faulthandler._sigsegv()
120""".strip(),
121 3,
122 'Segmentation fault')
123
Victor Stinnerd727e232011-04-01 12:13:55 +0200124 def test_sigabrt(self):
125 self.check_fatal_error("""
126import faulthandler
127faulthandler.enable()
128faulthandler._sigabrt()
129""".strip(),
130 3,
131 'Aborted')
132
Victor Stinner024e37a2011-03-31 01:31:06 +0200133 @unittest.skipIf(sys.platform == 'win32',
134 "SIGFPE cannot be caught on Windows")
135 def test_sigfpe(self):
136 self.check_fatal_error("""
137import faulthandler
138faulthandler.enable()
139faulthandler._sigfpe()
140""".strip(),
141 3,
142 'Floating point exception')
143
144 @unittest.skipIf(not hasattr(faulthandler, '_sigbus'),
145 "need faulthandler._sigbus()")
146 def test_sigbus(self):
147 self.check_fatal_error("""
148import faulthandler
149faulthandler.enable()
150faulthandler._sigbus()
151""".strip(),
152 3,
153 'Bus error')
154
155 @unittest.skipIf(not hasattr(faulthandler, '_sigill'),
156 "need faulthandler._sigill()")
157 def test_sigill(self):
158 self.check_fatal_error("""
159import faulthandler
160faulthandler.enable()
161faulthandler._sigill()
162""".strip(),
163 3,
164 'Illegal instruction')
165
166 def test_fatal_error(self):
167 self.check_fatal_error("""
168import faulthandler
169faulthandler._fatal_error(b'xyz')
170""".strip(),
171 2,
172 'xyz')
173
Victor Stinner024e37a2011-03-31 01:31:06 +0200174 @unittest.skipIf(not hasattr(faulthandler, '_stack_overflow'),
175 'need faulthandler._stack_overflow()')
176 def test_stack_overflow(self):
177 self.check_fatal_error("""
178import faulthandler
179faulthandler.enable()
180faulthandler._stack_overflow()
181""".strip(),
182 3,
Victor Stinnerf0480752011-03-31 11:34:08 +0200183 '(?:Segmentation fault|Bus error)',
184 other_regex='unable to raise a stack overflow')
Victor Stinner024e37a2011-03-31 01:31:06 +0200185
186 def test_gil_released(self):
187 self.check_fatal_error("""
188import faulthandler
189faulthandler.enable()
190faulthandler._read_null(True)
191""".strip(),
192 3,
193 '(?:Segmentation fault|Bus error)')
194
195 def test_enable_file(self):
196 with temporary_filename() as filename:
197 self.check_fatal_error("""
198import faulthandler
199output = open({filename}, 'wb')
200faulthandler.enable(output)
Victor Stinner44378d42011-04-01 15:37:12 +0200201faulthandler._read_null()
Victor Stinner024e37a2011-03-31 01:31:06 +0200202""".strip().format(filename=repr(filename)),
203 4,
204 '(?:Segmentation fault|Bus error)',
205 filename=filename)
206
Victor Stinner7bba62f2011-05-07 12:43:00 +0200207 def test_enable_single_thread(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200208 self.check_fatal_error("""
209import faulthandler
Victor Stinner7bba62f2011-05-07 12:43:00 +0200210faulthandler.enable(all_threads=False)
Victor Stinner44378d42011-04-01 15:37:12 +0200211faulthandler._read_null()
Victor Stinner024e37a2011-03-31 01:31:06 +0200212""".strip(),
213 3,
214 '(?:Segmentation fault|Bus error)',
Victor Stinner7bba62f2011-05-07 12:43:00 +0200215 all_threads=False)
Victor Stinner024e37a2011-03-31 01:31:06 +0200216
217 def test_disable(self):
218 code = """
219import faulthandler
220faulthandler.enable()
221faulthandler.disable()
222faulthandler._read_null()
223""".strip()
224 not_expected = 'Fatal Python error'
Victor Stinner05585cb2011-03-31 13:29:56 +0200225 stderr, exitcode = self.get_output(code)
Victor Stinner024e37a2011-03-31 01:31:06 +0200226 stder = '\n'.join(stderr)
227 self.assertTrue(not_expected not in stderr,
228 "%r is present in %r" % (not_expected, stderr))
Victor Stinner05585cb2011-03-31 13:29:56 +0200229 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200230
231 def test_is_enabled(self):
232 was_enabled = faulthandler.is_enabled()
233 try:
234 faulthandler.enable()
235 self.assertTrue(faulthandler.is_enabled())
236 faulthandler.disable()
237 self.assertFalse(faulthandler.is_enabled())
238 finally:
239 if was_enabled:
240 faulthandler.enable()
241 else:
242 faulthandler.disable()
243
244 def check_dump_traceback(self, filename):
245 """
246 Explicitly call dump_traceback() function and check its output.
247 Raise an error if the output doesn't match the expected format.
248 """
249 code = """
250import faulthandler
251
252def funcB():
253 if {has_filename}:
254 with open({filename}, "wb") as fp:
Victor Stinner7bba62f2011-05-07 12:43:00 +0200255 faulthandler.dump_traceback(fp, all_threads=False)
Victor Stinner024e37a2011-03-31 01:31:06 +0200256 else:
Victor Stinner7bba62f2011-05-07 12:43:00 +0200257 faulthandler.dump_traceback(all_threads=False)
Victor Stinner024e37a2011-03-31 01:31:06 +0200258
259def funcA():
260 funcB()
261
262funcA()
263""".strip()
264 code = code.format(
265 filename=repr(filename),
266 has_filename=bool(filename),
267 )
268 if filename:
269 lineno = 6
270 else:
271 lineno = 8
272 expected = [
273 'Traceback (most recent call first):',
274 ' File "<string>", line %s in funcB' % lineno,
275 ' File "<string>", line 11 in funcA',
276 ' File "<string>", line 13 in <module>'
277 ]
Victor Stinner05585cb2011-03-31 13:29:56 +0200278 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200279 self.assertEqual(trace, expected)
Victor Stinner05585cb2011-03-31 13:29:56 +0200280 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200281
282 def test_dump_traceback(self):
283 self.check_dump_traceback(None)
Victor Stinner19402332011-03-31 18:15:52 +0200284
285 def test_dump_traceback_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200286 with temporary_filename() as filename:
287 self.check_dump_traceback(filename)
288
Victor Stinnerff4cd882011-04-07 11:50:25 +0200289 @unittest.skipIf(not HAVE_THREADS, 'need threads')
Victor Stinner024e37a2011-03-31 01:31:06 +0200290 def check_dump_traceback_threads(self, filename):
291 """
292 Call explicitly dump_traceback(all_threads=True) and check the output.
293 Raise an error if the output doesn't match the expected format.
294 """
295 code = """
296import faulthandler
297from threading import Thread, Event
298import time
299
300def dump():
301 if {filename}:
302 with open({filename}, "wb") as fp:
303 faulthandler.dump_traceback(fp, all_threads=True)
304 else:
305 faulthandler.dump_traceback(all_threads=True)
306
307class Waiter(Thread):
308 # avoid blocking if the main thread raises an exception.
309 daemon = True
310
311 def __init__(self):
312 Thread.__init__(self)
313 self.running = Event()
314 self.stop = Event()
315
316 def run(self):
317 self.running.set()
318 self.stop.wait()
319
320waiter = Waiter()
321waiter.start()
322waiter.running.wait()
323dump()
324waiter.stop.set()
325waiter.join()
326""".strip()
327 code = code.format(filename=repr(filename))
Victor Stinner05585cb2011-03-31 13:29:56 +0200328 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200329 output = '\n'.join(output)
330 if filename:
331 lineno = 8
332 else:
333 lineno = 10
334 regex = """
335^Thread 0x[0-9a-f]+:
Victor Stinner1b3241f2011-04-03 18:41:22 +0200336(?: File ".*threading.py", line [0-9]+ in [_a-z]+
337){{1,3}} File "<string>", line 23 in run
Victor Stinner024e37a2011-03-31 01:31:06 +0200338 File ".*threading.py", line [0-9]+ in _bootstrap_inner
339 File ".*threading.py", line [0-9]+ in _bootstrap
340
341Current thread XXX:
342 File "<string>", line {lineno} in dump
343 File "<string>", line 28 in <module>$
344""".strip()
345 regex = regex.format(lineno=lineno)
346 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200347 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200348
349 def test_dump_traceback_threads(self):
350 self.check_dump_traceback_threads(None)
Victor Stinner19402332011-03-31 18:15:52 +0200351
352 def test_dump_traceback_threads_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200353 with temporary_filename() as filename:
354 self.check_dump_traceback_threads(filename)
355
Victor Stinnerde10f402011-04-08 12:57:06 +0200356 def _check_dump_tracebacks_later(self, repeat, cancel, filename, loops):
Victor Stinner024e37a2011-03-31 01:31:06 +0200357 """
358 Check how many times the traceback is written in timeout x 2.5 seconds,
359 or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending
360 on repeat and cancel options.
361
362 Raise an error if the output doesn't match the expect format.
363 """
Victor Stinnerc790a532011-04-08 13:39:59 +0200364 timeout_str = str(datetime.timedelta(seconds=TIMEOUT))
Victor Stinner024e37a2011-03-31 01:31:06 +0200365 code = """
366import faulthandler
367import time
368
Victor Stinnerde10f402011-04-08 12:57:06 +0200369def func(timeout, repeat, cancel, file, loops):
370 for loop in range(loops):
371 faulthandler.dump_tracebacks_later(timeout, repeat=repeat, file=file)
372 if cancel:
373 faulthandler.cancel_dump_tracebacks_later()
374 time.sleep(timeout * 2.5)
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200375 faulthandler.cancel_dump_tracebacks_later()
Victor Stinner024e37a2011-03-31 01:31:06 +0200376
Victor Stinner44378d42011-04-01 15:37:12 +0200377timeout = {timeout}
Victor Stinner024e37a2011-03-31 01:31:06 +0200378repeat = {repeat}
379cancel = {cancel}
Victor Stinnerde10f402011-04-08 12:57:06 +0200380loops = {loops}
Victor Stinner024e37a2011-03-31 01:31:06 +0200381if {has_filename}:
382 file = open({filename}, "wb")
383else:
384 file = None
Victor Stinnerde10f402011-04-08 12:57:06 +0200385func(timeout, repeat, cancel, file, loops)
Victor Stinner024e37a2011-03-31 01:31:06 +0200386if file is not None:
387 file.close()
388""".strip()
389 code = code.format(
Victor Stinnerde10f402011-04-08 12:57:06 +0200390 timeout=TIMEOUT,
Victor Stinner024e37a2011-03-31 01:31:06 +0200391 repeat=repeat,
392 cancel=cancel,
Victor Stinnerde10f402011-04-08 12:57:06 +0200393 loops=loops,
394 has_filename=bool(filename),
395 filename=repr(filename),
Victor Stinner024e37a2011-03-31 01:31:06 +0200396 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200397 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200398 trace = '\n'.join(trace)
399
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200400 if not cancel:
Victor Stinnerde10f402011-04-08 12:57:06 +0200401 count = loops
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200402 if repeat:
Victor Stinnerde10f402011-04-08 12:57:06 +0200403 count *= 2
Victor Stinnerc790a532011-04-08 13:39:59 +0200404 header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+:\n' % timeout_str
Victor Stinnerde10f402011-04-08 12:57:06 +0200405 regex = expected_traceback(9, 20, header, count=count)
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200406 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200407 else:
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200408 self.assertEqual(trace, '')
Victor Stinner05585cb2011-03-31 13:29:56 +0200409 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200410
411 @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
412 'need faulthandler.dump_tracebacks_later()')
413 def check_dump_tracebacks_later(self, repeat=False, cancel=False,
Victor Stinnerde10f402011-04-08 12:57:06 +0200414 file=False, twice=False):
415 if twice:
416 loops = 2
417 else:
418 loops = 1
Victor Stinner024e37a2011-03-31 01:31:06 +0200419 if file:
420 with temporary_filename() as filename:
Victor Stinnerde10f402011-04-08 12:57:06 +0200421 self._check_dump_tracebacks_later(repeat, cancel,
422 filename, loops)
Victor Stinner024e37a2011-03-31 01:31:06 +0200423 else:
Victor Stinnerde10f402011-04-08 12:57:06 +0200424 self._check_dump_tracebacks_later(repeat, cancel, None, loops)
Victor Stinner024e37a2011-03-31 01:31:06 +0200425
426 def test_dump_tracebacks_later(self):
427 self.check_dump_tracebacks_later()
428
429 def test_dump_tracebacks_later_repeat(self):
430 self.check_dump_tracebacks_later(repeat=True)
431
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200432 def test_dump_tracebacks_later_cancel(self):
433 self.check_dump_tracebacks_later(cancel=True)
Victor Stinner024e37a2011-03-31 01:31:06 +0200434
435 def test_dump_tracebacks_later_file(self):
436 self.check_dump_tracebacks_later(file=True)
437
Victor Stinnerde10f402011-04-08 12:57:06 +0200438 def test_dump_tracebacks_later_twice(self):
439 self.check_dump_tracebacks_later(twice=True)
440
Victor Stinner024e37a2011-03-31 01:31:06 +0200441 @unittest.skipIf(not hasattr(faulthandler, "register"),
442 "need faulthandler.register")
Victor Stinnera01ca122011-04-01 12:56:17 +0200443 def check_register(self, filename=False, all_threads=False,
444 unregister=False):
Victor Stinner024e37a2011-03-31 01:31:06 +0200445 """
446 Register a handler displaying the traceback on a user signal. Raise the
447 signal and check the written traceback.
448
449 Raise an error if the output doesn't match the expected format.
450 """
Victor Stinnera01ca122011-04-01 12:56:17 +0200451 signum = signal.SIGUSR1
Victor Stinner024e37a2011-03-31 01:31:06 +0200452 code = """
453import faulthandler
454import os
455import signal
456
457def func(signum):
458 os.kill(os.getpid(), signum)
459
Victor Stinnera01ca122011-04-01 12:56:17 +0200460signum = {signum}
461unregister = {unregister}
Victor Stinner024e37a2011-03-31 01:31:06 +0200462if {has_filename}:
463 file = open({filename}, "wb")
464else:
465 file = None
466faulthandler.register(signum, file=file, all_threads={all_threads})
Victor Stinnera01ca122011-04-01 12:56:17 +0200467if unregister:
468 faulthandler.unregister(signum)
Victor Stinner024e37a2011-03-31 01:31:06 +0200469func(signum)
470if file is not None:
471 file.close()
472""".strip()
473 code = code.format(
474 filename=repr(filename),
475 has_filename=bool(filename),
476 all_threads=all_threads,
Victor Stinnera01ca122011-04-01 12:56:17 +0200477 signum=signum,
478 unregister=unregister,
Victor Stinner024e37a2011-03-31 01:31:06 +0200479 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200480 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200481 trace = '\n'.join(trace)
Victor Stinnera01ca122011-04-01 12:56:17 +0200482 if not unregister:
483 if all_threads:
484 regex = 'Current thread XXX:\n'
485 else:
486 regex = 'Traceback \(most recent call first\):\n'
487 regex = expected_traceback(6, 17, regex)
488 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200489 else:
Victor Stinnera01ca122011-04-01 12:56:17 +0200490 self.assertEqual(trace, '')
491 if unregister:
492 self.assertNotEqual(exitcode, 0)
493 else:
494 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200495
496 def test_register(self):
497 self.check_register()
498
Victor Stinnera01ca122011-04-01 12:56:17 +0200499 def test_unregister(self):
500 self.check_register(unregister=True)
501
Victor Stinner024e37a2011-03-31 01:31:06 +0200502 def test_register_file(self):
503 with temporary_filename() as filename:
504 self.check_register(filename=filename)
505
506 def test_register_threads(self):
507 self.check_register(all_threads=True)
508
509
510def test_main():
511 support.run_unittest(FaultHandlerTests)
512
513if __name__ == "__main__":
514 test_main()