blob: 59a0a6d8dadd2524bc5b650f0b59e9ad8342b144 [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 Stinner44378d42011-04-01 15:37:12 +020011TIMEOUT = 0.5
12
Victor Stinner024e37a2011-03-31 01:31:06 +020013try:
14 from resource import setrlimit, RLIMIT_CORE, error as resource_error
15except ImportError:
16 prepare_subprocess = None
17else:
18 def prepare_subprocess():
19 # don't create core file
20 try:
21 setrlimit(RLIMIT_CORE, (0, 0))
22 except (ValueError, resource_error):
23 pass
24
25def expected_traceback(lineno1, lineno2, header, count=1):
26 regex = header
Victor Stinner7ad24e92011-03-31 22:35:49 +020027 regex += ' File "<string>", line %s in func\n' % lineno1
28 regex += ' File "<string>", line %s in <module>' % lineno2
Victor Stinner024e37a2011-03-31 01:31:06 +020029 if count != 1:
30 regex = (regex + '\n') * (count - 1) + regex
31 return '^' + regex + '$'
32
33@contextmanager
34def temporary_filename():
35 filename = tempfile.mktemp()
36 try:
37 yield filename
38 finally:
39 support.unlink(filename)
40
41class FaultHandlerTests(unittest.TestCase):
Victor Stinner05585cb2011-03-31 13:29:56 +020042 def get_output(self, code, filename=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020043 """
44 Run the specified code in Python (in a new child process) and read the
45 output from the standard error or from a file (if filename is set).
46 Return the output lines as a list.
47
48 Strip the reference count from the standard error for Python debug
49 build, and replace "Current thread 0x00007f8d8fbd9700" by "Current
50 thread XXX".
51 """
52 options = {}
53 if prepare_subprocess:
54 options['preexec_fn'] = prepare_subprocess
55 process = script_helper.spawn_python('-c', code, **options)
56 stdout, stderr = process.communicate()
57 exitcode = process.wait()
Victor Stinner19402332011-03-31 18:15:52 +020058 output = support.strip_python_stderr(stdout)
59 output = output.decode('ascii', 'backslashreplace')
Victor Stinner024e37a2011-03-31 01:31:06 +020060 if filename:
Victor Stinner19402332011-03-31 18:15:52 +020061 self.assertEqual(output, '')
Victor Stinner024e37a2011-03-31 01:31:06 +020062 with open(filename, "rb") as fp:
63 output = fp.read()
Victor Stinner19402332011-03-31 18:15:52 +020064 output = output.decode('ascii', 'backslashreplace')
Victor Stinner024e37a2011-03-31 01:31:06 +020065 output = re.sub('Current thread 0x[0-9a-f]+',
66 'Current thread XXX',
67 output)
Victor Stinner05585cb2011-03-31 13:29:56 +020068 return output.splitlines(), exitcode
Victor Stinner024e37a2011-03-31 01:31:06 +020069
70 def check_fatal_error(self, code, line_number, name_regex,
Victor Stinnerf0480752011-03-31 11:34:08 +020071 filename=None, all_threads=False, other_regex=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020072 """
73 Check that the fault handler for fatal errors is enabled and check the
74 traceback from the child process output.
75
76 Raise an error if the output doesn't match the expected format.
77 """
78 if all_threads:
79 header = 'Current thread XXX'
80 else:
81 header = 'Traceback (most recent call first)'
82 regex = """
83^Fatal Python error: {name}
84
85{header}:
86 File "<string>", line {lineno} in <module>$
87""".strip()
88 regex = regex.format(
89 lineno=line_number,
90 name=name_regex,
91 header=re.escape(header))
Victor Stinnerf0480752011-03-31 11:34:08 +020092 if other_regex:
93 regex += '|' + other_regex
Victor Stinner05585cb2011-03-31 13:29:56 +020094 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +020095 output = '\n'.join(output)
96 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +020097 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +020098
99 def test_read_null(self):
100 self.check_fatal_error("""
101import faulthandler
102faulthandler.enable()
103faulthandler._read_null()
104""".strip(),
105 3,
106 '(?:Segmentation fault|Bus error)')
107
108 def test_sigsegv(self):
109 self.check_fatal_error("""
110import faulthandler
111faulthandler.enable()
112faulthandler._sigsegv()
113""".strip(),
114 3,
115 'Segmentation fault')
116
Victor Stinnerd727e232011-04-01 12:13:55 +0200117 def test_sigabrt(self):
118 self.check_fatal_error("""
119import faulthandler
120faulthandler.enable()
121faulthandler._sigabrt()
122""".strip(),
123 3,
124 'Aborted')
125
Victor Stinner024e37a2011-03-31 01:31:06 +0200126 @unittest.skipIf(sys.platform == 'win32',
127 "SIGFPE cannot be caught on Windows")
128 def test_sigfpe(self):
129 self.check_fatal_error("""
130import faulthandler
131faulthandler.enable()
132faulthandler._sigfpe()
133""".strip(),
134 3,
135 'Floating point exception')
136
137 @unittest.skipIf(not hasattr(faulthandler, '_sigbus'),
138 "need faulthandler._sigbus()")
139 def test_sigbus(self):
140 self.check_fatal_error("""
141import faulthandler
142faulthandler.enable()
143faulthandler._sigbus()
144""".strip(),
145 3,
146 'Bus error')
147
148 @unittest.skipIf(not hasattr(faulthandler, '_sigill'),
149 "need faulthandler._sigill()")
150 def test_sigill(self):
151 self.check_fatal_error("""
152import faulthandler
153faulthandler.enable()
154faulthandler._sigill()
155""".strip(),
156 3,
157 'Illegal instruction')
158
159 def test_fatal_error(self):
160 self.check_fatal_error("""
161import faulthandler
162faulthandler._fatal_error(b'xyz')
163""".strip(),
164 2,
165 'xyz')
166
Victor Stinner024e37a2011-03-31 01:31:06 +0200167 @unittest.skipIf(not hasattr(faulthandler, '_stack_overflow'),
168 'need faulthandler._stack_overflow()')
169 def test_stack_overflow(self):
170 self.check_fatal_error("""
171import faulthandler
172faulthandler.enable()
173faulthandler._stack_overflow()
174""".strip(),
175 3,
Victor Stinnerf0480752011-03-31 11:34:08 +0200176 '(?:Segmentation fault|Bus error)',
177 other_regex='unable to raise a stack overflow')
Victor Stinner024e37a2011-03-31 01:31:06 +0200178
179 def test_gil_released(self):
180 self.check_fatal_error("""
181import faulthandler
182faulthandler.enable()
183faulthandler._read_null(True)
184""".strip(),
185 3,
186 '(?:Segmentation fault|Bus error)')
187
188 def test_enable_file(self):
189 with temporary_filename() as filename:
190 self.check_fatal_error("""
191import faulthandler
192output = open({filename}, 'wb')
193faulthandler.enable(output)
Victor Stinner44378d42011-04-01 15:37:12 +0200194faulthandler._read_null()
Victor Stinner024e37a2011-03-31 01:31:06 +0200195""".strip().format(filename=repr(filename)),
196 4,
197 '(?:Segmentation fault|Bus error)',
198 filename=filename)
199
200 def test_enable_threads(self):
201 self.check_fatal_error("""
202import faulthandler
203faulthandler.enable(all_threads=True)
Victor Stinner44378d42011-04-01 15:37:12 +0200204faulthandler._read_null()
Victor Stinner024e37a2011-03-31 01:31:06 +0200205""".strip(),
206 3,
207 '(?:Segmentation fault|Bus error)',
208 all_threads=True)
209
210 def test_disable(self):
211 code = """
212import faulthandler
213faulthandler.enable()
214faulthandler.disable()
215faulthandler._read_null()
216""".strip()
217 not_expected = 'Fatal Python error'
Victor Stinner05585cb2011-03-31 13:29:56 +0200218 stderr, exitcode = self.get_output(code)
Victor Stinner024e37a2011-03-31 01:31:06 +0200219 stder = '\n'.join(stderr)
220 self.assertTrue(not_expected not in stderr,
221 "%r is present in %r" % (not_expected, stderr))
Victor Stinner05585cb2011-03-31 13:29:56 +0200222 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200223
224 def test_is_enabled(self):
225 was_enabled = faulthandler.is_enabled()
226 try:
227 faulthandler.enable()
228 self.assertTrue(faulthandler.is_enabled())
229 faulthandler.disable()
230 self.assertFalse(faulthandler.is_enabled())
231 finally:
232 if was_enabled:
233 faulthandler.enable()
234 else:
235 faulthandler.disable()
236
237 def check_dump_traceback(self, filename):
238 """
239 Explicitly call dump_traceback() function and check its output.
240 Raise an error if the output doesn't match the expected format.
241 """
242 code = """
243import faulthandler
244
245def funcB():
246 if {has_filename}:
247 with open({filename}, "wb") as fp:
248 faulthandler.dump_traceback(fp)
249 else:
250 faulthandler.dump_traceback()
251
252def funcA():
253 funcB()
254
255funcA()
256""".strip()
257 code = code.format(
258 filename=repr(filename),
259 has_filename=bool(filename),
260 )
261 if filename:
262 lineno = 6
263 else:
264 lineno = 8
265 expected = [
266 'Traceback (most recent call first):',
267 ' File "<string>", line %s in funcB' % lineno,
268 ' File "<string>", line 11 in funcA',
269 ' File "<string>", line 13 in <module>'
270 ]
Victor Stinner05585cb2011-03-31 13:29:56 +0200271 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200272 self.assertEqual(trace, expected)
Victor Stinner05585cb2011-03-31 13:29:56 +0200273 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200274
275 def test_dump_traceback(self):
276 self.check_dump_traceback(None)
Victor Stinner19402332011-03-31 18:15:52 +0200277
278 def test_dump_traceback_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200279 with temporary_filename() as filename:
280 self.check_dump_traceback(filename)
281
282 def check_dump_traceback_threads(self, filename):
283 """
284 Call explicitly dump_traceback(all_threads=True) and check the output.
285 Raise an error if the output doesn't match the expected format.
286 """
287 code = """
288import faulthandler
289from threading import Thread, Event
290import time
291
292def dump():
293 if {filename}:
294 with open({filename}, "wb") as fp:
295 faulthandler.dump_traceback(fp, all_threads=True)
296 else:
297 faulthandler.dump_traceback(all_threads=True)
298
299class Waiter(Thread):
300 # avoid blocking if the main thread raises an exception.
301 daemon = True
302
303 def __init__(self):
304 Thread.__init__(self)
305 self.running = Event()
306 self.stop = Event()
307
308 def run(self):
309 self.running.set()
310 self.stop.wait()
311
312waiter = Waiter()
313waiter.start()
314waiter.running.wait()
315dump()
316waiter.stop.set()
317waiter.join()
318""".strip()
319 code = code.format(filename=repr(filename))
Victor Stinner05585cb2011-03-31 13:29:56 +0200320 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200321 output = '\n'.join(output)
322 if filename:
323 lineno = 8
324 else:
325 lineno = 10
326 regex = """
327^Thread 0x[0-9a-f]+:
Victor Stinner1b3241f2011-04-03 18:41:22 +0200328(?: File ".*threading.py", line [0-9]+ in [_a-z]+
329){{1,3}} File "<string>", line 23 in run
Victor Stinner024e37a2011-03-31 01:31:06 +0200330 File ".*threading.py", line [0-9]+ in _bootstrap_inner
331 File ".*threading.py", line [0-9]+ in _bootstrap
332
333Current thread XXX:
334 File "<string>", line {lineno} in dump
335 File "<string>", line 28 in <module>$
336""".strip()
337 regex = regex.format(lineno=lineno)
338 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200339 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200340
341 def test_dump_traceback_threads(self):
342 self.check_dump_traceback_threads(None)
Victor Stinner19402332011-03-31 18:15:52 +0200343
344 def test_dump_traceback_threads_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200345 with temporary_filename() as filename:
346 self.check_dump_traceback_threads(filename)
347
348 def _check_dump_tracebacks_later(self, repeat, cancel, filename):
349 """
350 Check how many times the traceback is written in timeout x 2.5 seconds,
351 or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending
352 on repeat and cancel options.
353
354 Raise an error if the output doesn't match the expect format.
355 """
356 code = """
357import faulthandler
358import time
359
360def func(repeat, cancel, timeout):
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200361 if cancel:
362 faulthandler.cancel_dump_tracebacks_later()
Victor Stinner9bdb43e2011-04-04 23:42:30 +0200363 time.sleep(timeout * 2.5)
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200364 faulthandler.cancel_dump_tracebacks_later()
Victor Stinner024e37a2011-03-31 01:31:06 +0200365
Victor Stinner44378d42011-04-01 15:37:12 +0200366timeout = {timeout}
Victor Stinner024e37a2011-03-31 01:31:06 +0200367repeat = {repeat}
368cancel = {cancel}
369if {has_filename}:
370 file = open({filename}, "wb")
371else:
372 file = None
373faulthandler.dump_tracebacks_later(timeout,
374 repeat=repeat, file=file)
375func(repeat, cancel, timeout)
376if file is not None:
377 file.close()
378""".strip()
379 code = code.format(
380 filename=repr(filename),
381 has_filename=bool(filename),
382 repeat=repeat,
383 cancel=cancel,
Victor Stinner44378d42011-04-01 15:37:12 +0200384 timeout=TIMEOUT,
Victor Stinner024e37a2011-03-31 01:31:06 +0200385 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200386 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200387 trace = '\n'.join(trace)
388
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200389 if not cancel:
390 if repeat:
391 count = 2
392 else:
393 count = 1
394 header = 'Thread 0x[0-9a-f]+:\n'
Victor Stinner9bdb43e2011-04-04 23:42:30 +0200395 regex = expected_traceback(7, 19, header, count=count)
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200396 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200397 else:
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200398 self.assertEqual(trace, '')
Victor Stinner05585cb2011-03-31 13:29:56 +0200399 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200400
401 @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
402 'need faulthandler.dump_tracebacks_later()')
403 def check_dump_tracebacks_later(self, repeat=False, cancel=False,
404 file=False):
405 if file:
406 with temporary_filename() as filename:
407 self._check_dump_tracebacks_later(repeat, cancel, filename)
408 else:
409 self._check_dump_tracebacks_later(repeat, cancel, None)
410
411 def test_dump_tracebacks_later(self):
412 self.check_dump_tracebacks_later()
413
414 def test_dump_tracebacks_later_repeat(self):
415 self.check_dump_tracebacks_later(repeat=True)
416
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200417 def test_dump_tracebacks_later_cancel(self):
418 self.check_dump_tracebacks_later(cancel=True)
Victor Stinner024e37a2011-03-31 01:31:06 +0200419
420 def test_dump_tracebacks_later_file(self):
421 self.check_dump_tracebacks_later(file=True)
422
423 @unittest.skipIf(not hasattr(faulthandler, "register"),
424 "need faulthandler.register")
Victor Stinnera01ca122011-04-01 12:56:17 +0200425 def check_register(self, filename=False, all_threads=False,
426 unregister=False):
Victor Stinner024e37a2011-03-31 01:31:06 +0200427 """
428 Register a handler displaying the traceback on a user signal. Raise the
429 signal and check the written traceback.
430
431 Raise an error if the output doesn't match the expected format.
432 """
Victor Stinnera01ca122011-04-01 12:56:17 +0200433 signum = signal.SIGUSR1
Victor Stinner024e37a2011-03-31 01:31:06 +0200434 code = """
435import faulthandler
436import os
437import signal
438
439def func(signum):
440 os.kill(os.getpid(), signum)
441
Victor Stinnera01ca122011-04-01 12:56:17 +0200442signum = {signum}
443unregister = {unregister}
Victor Stinner024e37a2011-03-31 01:31:06 +0200444if {has_filename}:
445 file = open({filename}, "wb")
446else:
447 file = None
448faulthandler.register(signum, file=file, all_threads={all_threads})
Victor Stinnera01ca122011-04-01 12:56:17 +0200449if unregister:
450 faulthandler.unregister(signum)
Victor Stinner024e37a2011-03-31 01:31:06 +0200451func(signum)
452if file is not None:
453 file.close()
454""".strip()
455 code = code.format(
456 filename=repr(filename),
457 has_filename=bool(filename),
458 all_threads=all_threads,
Victor Stinnera01ca122011-04-01 12:56:17 +0200459 signum=signum,
460 unregister=unregister,
Victor Stinner024e37a2011-03-31 01:31:06 +0200461 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200462 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200463 trace = '\n'.join(trace)
Victor Stinnera01ca122011-04-01 12:56:17 +0200464 if not unregister:
465 if all_threads:
466 regex = 'Current thread XXX:\n'
467 else:
468 regex = 'Traceback \(most recent call first\):\n'
469 regex = expected_traceback(6, 17, regex)
470 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200471 else:
Victor Stinnera01ca122011-04-01 12:56:17 +0200472 self.assertEqual(trace, '')
473 if unregister:
474 self.assertNotEqual(exitcode, 0)
475 else:
476 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200477
478 def test_register(self):
479 self.check_register()
480
Victor Stinnera01ca122011-04-01 12:56:17 +0200481 def test_unregister(self):
482 self.check_register(unregister=True)
483
Victor Stinner024e37a2011-03-31 01:31:06 +0200484 def test_register_file(self):
485 with temporary_filename() as filename:
486 self.check_register(filename=filename)
487
488 def test_register_threads(self):
489 self.check_register(all_threads=True)
490
491
492def test_main():
493 support.run_unittest(FaultHandlerTests)
494
495if __name__ == "__main__":
496 test_main()