blob: 291fac5126a4d7c0d4be5a624e233dbe43afc360 [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
Victor Stinner7e32f3a2011-06-29 13:44:05 +02004import os
Victor Stinner024e37a2011-03-31 01:31:06 +02005import re
6import signal
7import subprocess
8import sys
9from test import support, script_helper
10import tempfile
11import unittest
12
Victor Stinnerff4cd882011-04-07 11:50:25 +020013try:
14 import threading
15 HAVE_THREADS = True
16except ImportError:
17 HAVE_THREADS = False
18
Victor Stinner44378d42011-04-01 15:37:12 +020019TIMEOUT = 0.5
20
Victor Stinner024e37a2011-03-31 01:31:06 +020021try:
22 from resource import setrlimit, RLIMIT_CORE, error as resource_error
23except ImportError:
24 prepare_subprocess = None
25else:
26 def prepare_subprocess():
27 # don't create core file
28 try:
29 setrlimit(RLIMIT_CORE, (0, 0))
30 except (ValueError, resource_error):
31 pass
32
Victor Stinner301f3f02011-06-01 13:49:12 +020033def expected_traceback(lineno1, lineno2, header, min_count=1):
Victor Stinner024e37a2011-03-31 01:31:06 +020034 regex = header
Victor Stinner7ad24e92011-03-31 22:35:49 +020035 regex += ' File "<string>", line %s in func\n' % lineno1
36 regex += ' File "<string>", line %s in <module>' % lineno2
Victor Stinner301f3f02011-06-01 13:49:12 +020037 if 1 < min_count:
38 return '^' + (regex + '\n') * (min_count - 1) + regex
39 else:
40 return '^' + regex + '$'
Victor Stinner024e37a2011-03-31 01:31:06 +020041
42@contextmanager
43def temporary_filename():
44 filename = tempfile.mktemp()
45 try:
46 yield filename
47 finally:
48 support.unlink(filename)
49
50class FaultHandlerTests(unittest.TestCase):
Victor Stinner05585cb2011-03-31 13:29:56 +020051 def get_output(self, code, filename=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020052 """
53 Run the specified code in Python (in a new child process) and read the
54 output from the standard error or from a file (if filename is set).
55 Return the output lines as a list.
56
57 Strip the reference count from the standard error for Python debug
58 build, and replace "Current thread 0x00007f8d8fbd9700" by "Current
59 thread XXX".
60 """
61 options = {}
62 if prepare_subprocess:
63 options['preexec_fn'] = prepare_subprocess
64 process = script_helper.spawn_python('-c', code, **options)
65 stdout, stderr = process.communicate()
66 exitcode = process.wait()
Victor Stinner19402332011-03-31 18:15:52 +020067 output = support.strip_python_stderr(stdout)
68 output = output.decode('ascii', 'backslashreplace')
Victor Stinner024e37a2011-03-31 01:31:06 +020069 if filename:
Victor Stinner19402332011-03-31 18:15:52 +020070 self.assertEqual(output, '')
Victor Stinner024e37a2011-03-31 01:31:06 +020071 with open(filename, "rb") as fp:
72 output = fp.read()
Victor Stinner19402332011-03-31 18:15:52 +020073 output = output.decode('ascii', 'backslashreplace')
Victor Stinner024e37a2011-03-31 01:31:06 +020074 output = re.sub('Current thread 0x[0-9a-f]+',
75 'Current thread XXX',
76 output)
Victor Stinner05585cb2011-03-31 13:29:56 +020077 return output.splitlines(), exitcode
Victor Stinner024e37a2011-03-31 01:31:06 +020078
79 def check_fatal_error(self, code, line_number, name_regex,
Victor Stinner7bba62f2011-05-07 12:43:00 +020080 filename=None, all_threads=True, other_regex=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020081 """
82 Check that the fault handler for fatal errors is enabled and check the
83 traceback from the child process output.
84
85 Raise an error if the output doesn't match the expected format.
86 """
87 if all_threads:
88 header = 'Current thread XXX'
89 else:
90 header = 'Traceback (most recent call first)'
91 regex = """
92^Fatal Python error: {name}
93
94{header}:
95 File "<string>", line {lineno} in <module>$
96""".strip()
97 regex = regex.format(
98 lineno=line_number,
99 name=name_regex,
100 header=re.escape(header))
Victor Stinnerf0480752011-03-31 11:34:08 +0200101 if other_regex:
102 regex += '|' + other_regex
Victor Stinner05585cb2011-03-31 13:29:56 +0200103 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200104 output = '\n'.join(output)
105 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200106 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200107
108 def test_read_null(self):
109 self.check_fatal_error("""
110import faulthandler
111faulthandler.enable()
112faulthandler._read_null()
113""".strip(),
114 3,
115 '(?:Segmentation fault|Bus error)')
116
117 def test_sigsegv(self):
118 self.check_fatal_error("""
119import faulthandler
120faulthandler.enable()
121faulthandler._sigsegv()
122""".strip(),
123 3,
124 'Segmentation fault')
125
Victor Stinnerd727e232011-04-01 12:13:55 +0200126 def test_sigabrt(self):
127 self.check_fatal_error("""
128import faulthandler
129faulthandler.enable()
130faulthandler._sigabrt()
131""".strip(),
132 3,
133 'Aborted')
134
Victor Stinner024e37a2011-03-31 01:31:06 +0200135 @unittest.skipIf(sys.platform == 'win32',
136 "SIGFPE cannot be caught on Windows")
137 def test_sigfpe(self):
138 self.check_fatal_error("""
139import faulthandler
140faulthandler.enable()
141faulthandler._sigfpe()
142""".strip(),
143 3,
144 'Floating point exception')
145
146 @unittest.skipIf(not hasattr(faulthandler, '_sigbus'),
147 "need faulthandler._sigbus()")
148 def test_sigbus(self):
149 self.check_fatal_error("""
150import faulthandler
151faulthandler.enable()
152faulthandler._sigbus()
153""".strip(),
154 3,
155 'Bus error')
156
157 @unittest.skipIf(not hasattr(faulthandler, '_sigill'),
158 "need faulthandler._sigill()")
159 def test_sigill(self):
160 self.check_fatal_error("""
161import faulthandler
162faulthandler.enable()
163faulthandler._sigill()
164""".strip(),
165 3,
166 'Illegal instruction')
167
168 def test_fatal_error(self):
169 self.check_fatal_error("""
170import faulthandler
171faulthandler._fatal_error(b'xyz')
172""".strip(),
173 2,
174 'xyz')
175
Victor Stinner024e37a2011-03-31 01:31:06 +0200176 @unittest.skipIf(not hasattr(faulthandler, '_stack_overflow'),
177 'need faulthandler._stack_overflow()')
178 def test_stack_overflow(self):
179 self.check_fatal_error("""
180import faulthandler
181faulthandler.enable()
182faulthandler._stack_overflow()
183""".strip(),
184 3,
Victor Stinnerf0480752011-03-31 11:34:08 +0200185 '(?:Segmentation fault|Bus error)',
186 other_regex='unable to raise a stack overflow')
Victor Stinner024e37a2011-03-31 01:31:06 +0200187
188 def test_gil_released(self):
189 self.check_fatal_error("""
190import faulthandler
191faulthandler.enable()
192faulthandler._read_null(True)
193""".strip(),
194 3,
195 '(?:Segmentation fault|Bus error)')
196
197 def test_enable_file(self):
198 with temporary_filename() as filename:
199 self.check_fatal_error("""
200import faulthandler
201output = open({filename}, 'wb')
202faulthandler.enable(output)
Victor Stinner44378d42011-04-01 15:37:12 +0200203faulthandler._read_null()
Victor Stinner024e37a2011-03-31 01:31:06 +0200204""".strip().format(filename=repr(filename)),
205 4,
206 '(?:Segmentation fault|Bus error)',
207 filename=filename)
208
Victor Stinner7bba62f2011-05-07 12:43:00 +0200209 def test_enable_single_thread(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200210 self.check_fatal_error("""
211import faulthandler
Victor Stinner7bba62f2011-05-07 12:43:00 +0200212faulthandler.enable(all_threads=False)
Victor Stinner44378d42011-04-01 15:37:12 +0200213faulthandler._read_null()
Victor Stinner024e37a2011-03-31 01:31:06 +0200214""".strip(),
215 3,
216 '(?:Segmentation fault|Bus error)',
Victor Stinner7bba62f2011-05-07 12:43:00 +0200217 all_threads=False)
Victor Stinner024e37a2011-03-31 01:31:06 +0200218
219 def test_disable(self):
220 code = """
221import faulthandler
222faulthandler.enable()
223faulthandler.disable()
224faulthandler._read_null()
225""".strip()
226 not_expected = 'Fatal Python error'
Victor Stinner05585cb2011-03-31 13:29:56 +0200227 stderr, exitcode = self.get_output(code)
Victor Stinner024e37a2011-03-31 01:31:06 +0200228 stder = '\n'.join(stderr)
229 self.assertTrue(not_expected not in stderr,
230 "%r is present in %r" % (not_expected, stderr))
Victor Stinner05585cb2011-03-31 13:29:56 +0200231 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200232
233 def test_is_enabled(self):
Victor Stinner7e32f3a2011-06-29 13:44:05 +0200234 null_stderr = None
235 orig_stderr = sys.stderr
Victor Stinner024e37a2011-03-31 01:31:06 +0200236 try:
Victor Stinner7e32f3a2011-06-29 13:44:05 +0200237 # regrtest may replace sys.stderr by io.StringIO object, but
238 # faulthandler.enable() requires that sys.stderr has a fileno()
239 # method
240 null_stderr = open(os.devnull, 'w')
241 sys.stderr = null_stderr
242
243 was_enabled = faulthandler.is_enabled()
244 try:
Victor Stinner024e37a2011-03-31 01:31:06 +0200245 faulthandler.enable()
Victor Stinner7e32f3a2011-06-29 13:44:05 +0200246 self.assertTrue(faulthandler.is_enabled())
Victor Stinner024e37a2011-03-31 01:31:06 +0200247 faulthandler.disable()
Victor Stinner7e32f3a2011-06-29 13:44:05 +0200248 self.assertFalse(faulthandler.is_enabled())
249 finally:
250 if was_enabled:
251 faulthandler.enable()
252 else:
253 faulthandler.disable()
254 finally:
255 sys.stderr = orig_stderr
256 if null_stderr is not None:
257 null_stderr.close()
Victor Stinner024e37a2011-03-31 01:31:06 +0200258
259 def check_dump_traceback(self, filename):
260 """
261 Explicitly call dump_traceback() function and check its output.
262 Raise an error if the output doesn't match the expected format.
263 """
264 code = """
265import faulthandler
266
267def funcB():
268 if {has_filename}:
269 with open({filename}, "wb") as fp:
Victor Stinner7bba62f2011-05-07 12:43:00 +0200270 faulthandler.dump_traceback(fp, all_threads=False)
Victor Stinner024e37a2011-03-31 01:31:06 +0200271 else:
Victor Stinner7bba62f2011-05-07 12:43:00 +0200272 faulthandler.dump_traceback(all_threads=False)
Victor Stinner024e37a2011-03-31 01:31:06 +0200273
274def funcA():
275 funcB()
276
277funcA()
278""".strip()
279 code = code.format(
280 filename=repr(filename),
281 has_filename=bool(filename),
282 )
283 if filename:
284 lineno = 6
285 else:
286 lineno = 8
287 expected = [
288 'Traceback (most recent call first):',
289 ' File "<string>", line %s in funcB' % lineno,
290 ' File "<string>", line 11 in funcA',
291 ' File "<string>", line 13 in <module>'
292 ]
Victor Stinner05585cb2011-03-31 13:29:56 +0200293 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200294 self.assertEqual(trace, expected)
Victor Stinner05585cb2011-03-31 13:29:56 +0200295 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200296
297 def test_dump_traceback(self):
298 self.check_dump_traceback(None)
Victor Stinner19402332011-03-31 18:15:52 +0200299
300 def test_dump_traceback_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200301 with temporary_filename() as filename:
302 self.check_dump_traceback(filename)
303
Victor Stinnerff4cd882011-04-07 11:50:25 +0200304 @unittest.skipIf(not HAVE_THREADS, 'need threads')
Victor Stinner024e37a2011-03-31 01:31:06 +0200305 def check_dump_traceback_threads(self, filename):
306 """
307 Call explicitly dump_traceback(all_threads=True) and check the output.
308 Raise an error if the output doesn't match the expected format.
309 """
310 code = """
311import faulthandler
312from threading import Thread, Event
313import time
314
315def dump():
316 if {filename}:
317 with open({filename}, "wb") as fp:
318 faulthandler.dump_traceback(fp, all_threads=True)
319 else:
320 faulthandler.dump_traceback(all_threads=True)
321
322class Waiter(Thread):
323 # avoid blocking if the main thread raises an exception.
324 daemon = True
325
326 def __init__(self):
327 Thread.__init__(self)
328 self.running = Event()
329 self.stop = Event()
330
331 def run(self):
332 self.running.set()
333 self.stop.wait()
334
335waiter = Waiter()
336waiter.start()
337waiter.running.wait()
338dump()
339waiter.stop.set()
340waiter.join()
341""".strip()
342 code = code.format(filename=repr(filename))
Victor Stinner05585cb2011-03-31 13:29:56 +0200343 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200344 output = '\n'.join(output)
345 if filename:
346 lineno = 8
347 else:
348 lineno = 10
349 regex = """
350^Thread 0x[0-9a-f]+:
Victor Stinner1b3241f2011-04-03 18:41:22 +0200351(?: File ".*threading.py", line [0-9]+ in [_a-z]+
352){{1,3}} File "<string>", line 23 in run
Victor Stinner024e37a2011-03-31 01:31:06 +0200353 File ".*threading.py", line [0-9]+ in _bootstrap_inner
354 File ".*threading.py", line [0-9]+ in _bootstrap
355
356Current thread XXX:
357 File "<string>", line {lineno} in dump
358 File "<string>", line 28 in <module>$
359""".strip()
360 regex = regex.format(lineno=lineno)
361 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200362 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200363
364 def test_dump_traceback_threads(self):
365 self.check_dump_traceback_threads(None)
Victor Stinner19402332011-03-31 18:15:52 +0200366
367 def test_dump_traceback_threads_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200368 with temporary_filename() as filename:
369 self.check_dump_traceback_threads(filename)
370
Victor Stinnerde10f402011-04-08 12:57:06 +0200371 def _check_dump_tracebacks_later(self, repeat, cancel, filename, loops):
Victor Stinner024e37a2011-03-31 01:31:06 +0200372 """
373 Check how many times the traceback is written in timeout x 2.5 seconds,
374 or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending
375 on repeat and cancel options.
376
377 Raise an error if the output doesn't match the expect format.
378 """
Victor Stinnerc790a532011-04-08 13:39:59 +0200379 timeout_str = str(datetime.timedelta(seconds=TIMEOUT))
Victor Stinner024e37a2011-03-31 01:31:06 +0200380 code = """
381import faulthandler
382import time
383
Victor Stinnerde10f402011-04-08 12:57:06 +0200384def func(timeout, repeat, cancel, file, loops):
385 for loop in range(loops):
386 faulthandler.dump_tracebacks_later(timeout, repeat=repeat, file=file)
387 if cancel:
388 faulthandler.cancel_dump_tracebacks_later()
Victor Stinner301f3f02011-06-01 13:49:12 +0200389 time.sleep(timeout * 5)
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200390 faulthandler.cancel_dump_tracebacks_later()
Victor Stinner024e37a2011-03-31 01:31:06 +0200391
Victor Stinner44378d42011-04-01 15:37:12 +0200392timeout = {timeout}
Victor Stinner024e37a2011-03-31 01:31:06 +0200393repeat = {repeat}
394cancel = {cancel}
Victor Stinnerde10f402011-04-08 12:57:06 +0200395loops = {loops}
Victor Stinner024e37a2011-03-31 01:31:06 +0200396if {has_filename}:
397 file = open({filename}, "wb")
398else:
399 file = None
Victor Stinnerde10f402011-04-08 12:57:06 +0200400func(timeout, repeat, cancel, file, loops)
Victor Stinner024e37a2011-03-31 01:31:06 +0200401if file is not None:
402 file.close()
403""".strip()
404 code = code.format(
Victor Stinnerde10f402011-04-08 12:57:06 +0200405 timeout=TIMEOUT,
Victor Stinner024e37a2011-03-31 01:31:06 +0200406 repeat=repeat,
407 cancel=cancel,
Victor Stinnerde10f402011-04-08 12:57:06 +0200408 loops=loops,
409 has_filename=bool(filename),
410 filename=repr(filename),
Victor Stinner024e37a2011-03-31 01:31:06 +0200411 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200412 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200413 trace = '\n'.join(trace)
414
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200415 if not cancel:
Victor Stinnerde10f402011-04-08 12:57:06 +0200416 count = loops
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200417 if repeat:
Victor Stinnerde10f402011-04-08 12:57:06 +0200418 count *= 2
Victor Stinnerc790a532011-04-08 13:39:59 +0200419 header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+:\n' % timeout_str
Victor Stinner301f3f02011-06-01 13:49:12 +0200420 regex = expected_traceback(9, 20, header, min_count=count)
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200421 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200422 else:
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200423 self.assertEqual(trace, '')
Victor Stinner05585cb2011-03-31 13:29:56 +0200424 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200425
426 @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
427 'need faulthandler.dump_tracebacks_later()')
428 def check_dump_tracebacks_later(self, repeat=False, cancel=False,
Victor Stinnerde10f402011-04-08 12:57:06 +0200429 file=False, twice=False):
430 if twice:
431 loops = 2
432 else:
433 loops = 1
Victor Stinner024e37a2011-03-31 01:31:06 +0200434 if file:
435 with temporary_filename() as filename:
Victor Stinnerde10f402011-04-08 12:57:06 +0200436 self._check_dump_tracebacks_later(repeat, cancel,
437 filename, loops)
Victor Stinner024e37a2011-03-31 01:31:06 +0200438 else:
Victor Stinnerde10f402011-04-08 12:57:06 +0200439 self._check_dump_tracebacks_later(repeat, cancel, None, loops)
Victor Stinner024e37a2011-03-31 01:31:06 +0200440
441 def test_dump_tracebacks_later(self):
442 self.check_dump_tracebacks_later()
443
444 def test_dump_tracebacks_later_repeat(self):
445 self.check_dump_tracebacks_later(repeat=True)
446
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200447 def test_dump_tracebacks_later_cancel(self):
448 self.check_dump_tracebacks_later(cancel=True)
Victor Stinner024e37a2011-03-31 01:31:06 +0200449
450 def test_dump_tracebacks_later_file(self):
451 self.check_dump_tracebacks_later(file=True)
452
Victor Stinnerde10f402011-04-08 12:57:06 +0200453 def test_dump_tracebacks_later_twice(self):
454 self.check_dump_tracebacks_later(twice=True)
455
Victor Stinner024e37a2011-03-31 01:31:06 +0200456 @unittest.skipIf(not hasattr(faulthandler, "register"),
457 "need faulthandler.register")
Victor Stinnera01ca122011-04-01 12:56:17 +0200458 def check_register(self, filename=False, all_threads=False,
459 unregister=False):
Victor Stinner024e37a2011-03-31 01:31:06 +0200460 """
461 Register a handler displaying the traceback on a user signal. Raise the
462 signal and check the written traceback.
463
464 Raise an error if the output doesn't match the expected format.
465 """
Victor Stinnera01ca122011-04-01 12:56:17 +0200466 signum = signal.SIGUSR1
Victor Stinner024e37a2011-03-31 01:31:06 +0200467 code = """
468import faulthandler
469import os
470import signal
471
472def func(signum):
473 os.kill(os.getpid(), signum)
474
Victor Stinnera01ca122011-04-01 12:56:17 +0200475signum = {signum}
476unregister = {unregister}
Victor Stinner024e37a2011-03-31 01:31:06 +0200477if {has_filename}:
478 file = open({filename}, "wb")
479else:
480 file = None
481faulthandler.register(signum, file=file, all_threads={all_threads})
Victor Stinnera01ca122011-04-01 12:56:17 +0200482if unregister:
483 faulthandler.unregister(signum)
Victor Stinner024e37a2011-03-31 01:31:06 +0200484func(signum)
485if file is not None:
486 file.close()
487""".strip()
488 code = code.format(
489 filename=repr(filename),
490 has_filename=bool(filename),
491 all_threads=all_threads,
Victor Stinnera01ca122011-04-01 12:56:17 +0200492 signum=signum,
493 unregister=unregister,
Victor Stinner024e37a2011-03-31 01:31:06 +0200494 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200495 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200496 trace = '\n'.join(trace)
Victor Stinnera01ca122011-04-01 12:56:17 +0200497 if not unregister:
498 if all_threads:
499 regex = 'Current thread XXX:\n'
500 else:
501 regex = 'Traceback \(most recent call first\):\n'
502 regex = expected_traceback(6, 17, regex)
503 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200504 else:
Victor Stinnera01ca122011-04-01 12:56:17 +0200505 self.assertEqual(trace, '')
506 if unregister:
507 self.assertNotEqual(exitcode, 0)
508 else:
509 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200510
511 def test_register(self):
512 self.check_register()
513
Victor Stinnera01ca122011-04-01 12:56:17 +0200514 def test_unregister(self):
515 self.check_register(unregister=True)
516
Victor Stinner024e37a2011-03-31 01:31:06 +0200517 def test_register_file(self):
518 with temporary_filename() as filename:
519 self.check_register(filename=filename)
520
521 def test_register_threads(self):
522 self.check_register(all_threads=True)
523
524
525def test_main():
526 support.run_unittest(FaultHandlerTests)
527
528if __name__ == "__main__":
529 test_main()