blob: 2730aff7c028ab5db6e7ef21b87f5abbdbf81d35 [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):
361 pause = timeout * 2.5
Victor Stinner7ad24e92011-03-31 22:35:49 +0200362 # on Windows XP, b-a gives 1.249931 after sleep(1.25)
363 min_pause = pause * 0.9
Victor Stinner024e37a2011-03-31 01:31:06 +0200364 a = time.time()
365 time.sleep(pause)
366 faulthandler.cancel_dump_tracebacks_later()
367 b = time.time()
368 # Check that sleep() was not interrupted
Victor Stinner7ad24e92011-03-31 22:35:49 +0200369 assert (b - a) >= min_pause, "{{}} < {{}}".format(b - a, min_pause)
Victor Stinner024e37a2011-03-31 01:31:06 +0200370
371 if cancel:
372 pause = timeout * 1.5
Victor Stinner7ad24e92011-03-31 22:35:49 +0200373 min_pause = pause * 0.9
Victor Stinner024e37a2011-03-31 01:31:06 +0200374 a = time.time()
375 time.sleep(pause)
376 b = time.time()
377 # Check that sleep() was not interrupted
Victor Stinner7ad24e92011-03-31 22:35:49 +0200378 assert (b - a) >= min_pause, "{{}} < {{}}".format(b - a, min_pause)
Victor Stinner024e37a2011-03-31 01:31:06 +0200379
Victor Stinner44378d42011-04-01 15:37:12 +0200380timeout = {timeout}
Victor Stinner024e37a2011-03-31 01:31:06 +0200381repeat = {repeat}
382cancel = {cancel}
383if {has_filename}:
384 file = open({filename}, "wb")
385else:
386 file = None
387faulthandler.dump_tracebacks_later(timeout,
388 repeat=repeat, file=file)
389func(repeat, cancel, timeout)
390if file is not None:
391 file.close()
392""".strip()
393 code = code.format(
394 filename=repr(filename),
395 has_filename=bool(filename),
396 repeat=repeat,
397 cancel=cancel,
Victor Stinner44378d42011-04-01 15:37:12 +0200398 timeout=TIMEOUT,
Victor Stinner024e37a2011-03-31 01:31:06 +0200399 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200400 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200401 trace = '\n'.join(trace)
402
403 if repeat:
404 count = 2
405 else:
406 count = 1
407 header = 'Thread 0x[0-9a-f]+:\n'
Victor Stinner7ad24e92011-03-31 22:35:49 +0200408 regex = expected_traceback(9, 33, header, count=count)
409 self.assertRegex(trace, regex)
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,
415 file=False):
416 if file:
417 with temporary_filename() as filename:
418 self._check_dump_tracebacks_later(repeat, cancel, filename)
419 else:
420 self._check_dump_tracebacks_later(repeat, cancel, None)
421
422 def test_dump_tracebacks_later(self):
423 self.check_dump_tracebacks_later()
424
425 def test_dump_tracebacks_later_repeat(self):
426 self.check_dump_tracebacks_later(repeat=True)
427
428 def test_dump_tracebacks_later_repeat_cancel(self):
429 self.check_dump_tracebacks_later(repeat=True, cancel=True)
430
431 def test_dump_tracebacks_later_file(self):
432 self.check_dump_tracebacks_later(file=True)
433
434 @unittest.skipIf(not hasattr(faulthandler, "register"),
435 "need faulthandler.register")
Victor Stinnera01ca122011-04-01 12:56:17 +0200436 def check_register(self, filename=False, all_threads=False,
437 unregister=False):
Victor Stinner024e37a2011-03-31 01:31:06 +0200438 """
439 Register a handler displaying the traceback on a user signal. Raise the
440 signal and check the written traceback.
441
442 Raise an error if the output doesn't match the expected format.
443 """
Victor Stinnera01ca122011-04-01 12:56:17 +0200444 signum = signal.SIGUSR1
Victor Stinner024e37a2011-03-31 01:31:06 +0200445 code = """
446import faulthandler
447import os
448import signal
449
450def func(signum):
451 os.kill(os.getpid(), signum)
452
Victor Stinnera01ca122011-04-01 12:56:17 +0200453signum = {signum}
454unregister = {unregister}
Victor Stinner024e37a2011-03-31 01:31:06 +0200455if {has_filename}:
456 file = open({filename}, "wb")
457else:
458 file = None
459faulthandler.register(signum, file=file, all_threads={all_threads})
Victor Stinnera01ca122011-04-01 12:56:17 +0200460if unregister:
461 faulthandler.unregister(signum)
Victor Stinner024e37a2011-03-31 01:31:06 +0200462func(signum)
463if file is not None:
464 file.close()
465""".strip()
466 code = code.format(
467 filename=repr(filename),
468 has_filename=bool(filename),
469 all_threads=all_threads,
Victor Stinnera01ca122011-04-01 12:56:17 +0200470 signum=signum,
471 unregister=unregister,
Victor Stinner024e37a2011-03-31 01:31:06 +0200472 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200473 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200474 trace = '\n'.join(trace)
Victor Stinnera01ca122011-04-01 12:56:17 +0200475 if not unregister:
476 if all_threads:
477 regex = 'Current thread XXX:\n'
478 else:
479 regex = 'Traceback \(most recent call first\):\n'
480 regex = expected_traceback(6, 17, regex)
481 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200482 else:
Victor Stinnera01ca122011-04-01 12:56:17 +0200483 self.assertEqual(trace, '')
484 if unregister:
485 self.assertNotEqual(exitcode, 0)
486 else:
487 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200488
489 def test_register(self):
490 self.check_register()
491
Victor Stinnera01ca122011-04-01 12:56:17 +0200492 def test_unregister(self):
493 self.check_register(unregister=True)
494
Victor Stinner024e37a2011-03-31 01:31:06 +0200495 def test_register_file(self):
496 with temporary_filename() as filename:
497 self.check_register(filename=filename)
498
499 def test_register_threads(self):
500 self.check_register(all_threads=True)
501
502
503def test_main():
504 support.run_unittest(FaultHandlerTests)
505
506if __name__ == "__main__":
507 test_main()