blob: 4158d864aa698f572011919eedc899f631f4c263 [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()
363
Victor Stinner024e37a2011-03-31 01:31:06 +0200364 pause = timeout * 2.5
Victor Stinner7ad24e92011-03-31 22:35:49 +0200365 # on Windows XP, b-a gives 1.249931 after sleep(1.25)
366 min_pause = pause * 0.9
Victor Stinner024e37a2011-03-31 01:31:06 +0200367 a = time.time()
368 time.sleep(pause)
Victor Stinner024e37a2011-03-31 01:31:06 +0200369 b = time.time()
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200370 faulthandler.cancel_dump_tracebacks_later()
Victor Stinner024e37a2011-03-31 01:31:06 +0200371 # Check that sleep() was not interrupted
Victor Stinner7ad24e92011-03-31 22:35:49 +0200372 assert (b - a) >= min_pause, "{{}} < {{}}".format(b - a, min_pause)
Victor Stinner024e37a2011-03-31 01:31:06 +0200373
Victor Stinner44378d42011-04-01 15:37:12 +0200374timeout = {timeout}
Victor Stinner024e37a2011-03-31 01:31:06 +0200375repeat = {repeat}
376cancel = {cancel}
377if {has_filename}:
378 file = open({filename}, "wb")
379else:
380 file = None
381faulthandler.dump_tracebacks_later(timeout,
382 repeat=repeat, file=file)
383func(repeat, cancel, timeout)
384if file is not None:
385 file.close()
386""".strip()
387 code = code.format(
388 filename=repr(filename),
389 has_filename=bool(filename),
390 repeat=repeat,
391 cancel=cancel,
Victor Stinner44378d42011-04-01 15:37:12 +0200392 timeout=TIMEOUT,
Victor Stinner024e37a2011-03-31 01:31:06 +0200393 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200394 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200395 trace = '\n'.join(trace)
396
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200397 if not cancel:
398 if repeat:
399 count = 2
400 else:
401 count = 1
402 header = 'Thread 0x[0-9a-f]+:\n'
403 regex = expected_traceback(12, 27, header, count=count)
404 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,
412 file=False):
413 if file:
414 with temporary_filename() as filename:
415 self._check_dump_tracebacks_later(repeat, cancel, filename)
416 else:
417 self._check_dump_tracebacks_later(repeat, cancel, None)
418
419 def test_dump_tracebacks_later(self):
420 self.check_dump_tracebacks_later()
421
422 def test_dump_tracebacks_later_repeat(self):
423 self.check_dump_tracebacks_later(repeat=True)
424
Victor Stinnerf77ccc62011-04-03 18:45:42 +0200425 def test_dump_tracebacks_later_cancel(self):
426 self.check_dump_tracebacks_later(cancel=True)
Victor Stinner024e37a2011-03-31 01:31:06 +0200427
428 def test_dump_tracebacks_later_file(self):
429 self.check_dump_tracebacks_later(file=True)
430
431 @unittest.skipIf(not hasattr(faulthandler, "register"),
432 "need faulthandler.register")
Victor Stinnera01ca122011-04-01 12:56:17 +0200433 def check_register(self, filename=False, all_threads=False,
434 unregister=False):
Victor Stinner024e37a2011-03-31 01:31:06 +0200435 """
436 Register a handler displaying the traceback on a user signal. Raise the
437 signal and check the written traceback.
438
439 Raise an error if the output doesn't match the expected format.
440 """
Victor Stinnera01ca122011-04-01 12:56:17 +0200441 signum = signal.SIGUSR1
Victor Stinner024e37a2011-03-31 01:31:06 +0200442 code = """
443import faulthandler
444import os
445import signal
446
447def func(signum):
448 os.kill(os.getpid(), signum)
449
Victor Stinnera01ca122011-04-01 12:56:17 +0200450signum = {signum}
451unregister = {unregister}
Victor Stinner024e37a2011-03-31 01:31:06 +0200452if {has_filename}:
453 file = open({filename}, "wb")
454else:
455 file = None
456faulthandler.register(signum, file=file, all_threads={all_threads})
Victor Stinnera01ca122011-04-01 12:56:17 +0200457if unregister:
458 faulthandler.unregister(signum)
Victor Stinner024e37a2011-03-31 01:31:06 +0200459func(signum)
460if file is not None:
461 file.close()
462""".strip()
463 code = code.format(
464 filename=repr(filename),
465 has_filename=bool(filename),
466 all_threads=all_threads,
Victor Stinnera01ca122011-04-01 12:56:17 +0200467 signum=signum,
468 unregister=unregister,
Victor Stinner024e37a2011-03-31 01:31:06 +0200469 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200470 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200471 trace = '\n'.join(trace)
Victor Stinnera01ca122011-04-01 12:56:17 +0200472 if not unregister:
473 if all_threads:
474 regex = 'Current thread XXX:\n'
475 else:
476 regex = 'Traceback \(most recent call first\):\n'
477 regex = expected_traceback(6, 17, regex)
478 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200479 else:
Victor Stinnera01ca122011-04-01 12:56:17 +0200480 self.assertEqual(trace, '')
481 if unregister:
482 self.assertNotEqual(exitcode, 0)
483 else:
484 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200485
486 def test_register(self):
487 self.check_register()
488
Victor Stinnera01ca122011-04-01 12:56:17 +0200489 def test_unregister(self):
490 self.check_register(unregister=True)
491
Victor Stinner024e37a2011-03-31 01:31:06 +0200492 def test_register_file(self):
493 with temporary_filename() as filename:
494 self.check_register(filename=filename)
495
496 def test_register_threads(self):
497 self.check_register(all_threads=True)
498
499
500def test_main():
501 support.run_unittest(FaultHandlerTests)
502
503if __name__ == "__main__":
504 test_main()