blob: a919900546e72d1a52ea2ea1ea3d257c72ff8392 [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]+:
328(?: File ".*threading.py", line [0-9]+ in wait
329)? File ".*threading.py", line [0-9]+ in wait
330 File "<string>", line 23 in run
331 File ".*threading.py", line [0-9]+ in _bootstrap_inner
332 File ".*threading.py", line [0-9]+ in _bootstrap
333
334Current thread XXX:
335 File "<string>", line {lineno} in dump
336 File "<string>", line 28 in <module>$
337""".strip()
338 regex = regex.format(lineno=lineno)
339 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200340 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200341
342 def test_dump_traceback_threads(self):
343 self.check_dump_traceback_threads(None)
Victor Stinner19402332011-03-31 18:15:52 +0200344
345 def test_dump_traceback_threads_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200346 with temporary_filename() as filename:
347 self.check_dump_traceback_threads(filename)
348
349 def _check_dump_tracebacks_later(self, repeat, cancel, filename):
350 """
351 Check how many times the traceback is written in timeout x 2.5 seconds,
352 or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending
353 on repeat and cancel options.
354
355 Raise an error if the output doesn't match the expect format.
356 """
357 code = """
358import faulthandler
359import time
360
361def func(repeat, cancel, timeout):
362 pause = timeout * 2.5
Victor Stinner7ad24e92011-03-31 22:35:49 +0200363 # on Windows XP, b-a gives 1.249931 after sleep(1.25)
364 min_pause = pause * 0.9
Victor Stinner024e37a2011-03-31 01:31:06 +0200365 a = time.time()
366 time.sleep(pause)
367 faulthandler.cancel_dump_tracebacks_later()
368 b = time.time()
369 # Check that sleep() was not interrupted
Victor Stinner7ad24e92011-03-31 22:35:49 +0200370 assert (b - a) >= min_pause, "{{}} < {{}}".format(b - a, min_pause)
Victor Stinner024e37a2011-03-31 01:31:06 +0200371
372 if cancel:
373 pause = timeout * 1.5
Victor Stinner7ad24e92011-03-31 22:35:49 +0200374 min_pause = pause * 0.9
Victor Stinner024e37a2011-03-31 01:31:06 +0200375 a = time.time()
376 time.sleep(pause)
377 b = time.time()
378 # Check that sleep() was not interrupted
Victor Stinner7ad24e92011-03-31 22:35:49 +0200379 assert (b - a) >= min_pause, "{{}} < {{}}".format(b - a, min_pause)
Victor Stinner024e37a2011-03-31 01:31:06 +0200380
Victor Stinner44378d42011-04-01 15:37:12 +0200381timeout = {timeout}
Victor Stinner024e37a2011-03-31 01:31:06 +0200382repeat = {repeat}
383cancel = {cancel}
384if {has_filename}:
385 file = open({filename}, "wb")
386else:
387 file = None
388faulthandler.dump_tracebacks_later(timeout,
389 repeat=repeat, file=file)
390func(repeat, cancel, timeout)
391if file is not None:
392 file.close()
393""".strip()
394 code = code.format(
395 filename=repr(filename),
396 has_filename=bool(filename),
397 repeat=repeat,
398 cancel=cancel,
Victor Stinner44378d42011-04-01 15:37:12 +0200399 timeout=TIMEOUT,
Victor Stinner024e37a2011-03-31 01:31:06 +0200400 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200401 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200402 trace = '\n'.join(trace)
403
404 if repeat:
405 count = 2
406 else:
407 count = 1
408 header = 'Thread 0x[0-9a-f]+:\n'
Victor Stinner7ad24e92011-03-31 22:35:49 +0200409 regex = expected_traceback(9, 33, header, count=count)
410 self.assertRegex(trace, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200411 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200412
413 @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
414 'need faulthandler.dump_tracebacks_later()')
415 def check_dump_tracebacks_later(self, repeat=False, cancel=False,
416 file=False):
417 if file:
418 with temporary_filename() as filename:
419 self._check_dump_tracebacks_later(repeat, cancel, filename)
420 else:
421 self._check_dump_tracebacks_later(repeat, cancel, None)
422
423 def test_dump_tracebacks_later(self):
424 self.check_dump_tracebacks_later()
425
426 def test_dump_tracebacks_later_repeat(self):
427 self.check_dump_tracebacks_later(repeat=True)
428
429 def test_dump_tracebacks_later_repeat_cancel(self):
430 self.check_dump_tracebacks_later(repeat=True, cancel=True)
431
432 def test_dump_tracebacks_later_file(self):
433 self.check_dump_tracebacks_later(file=True)
434
435 @unittest.skipIf(not hasattr(faulthandler, "register"),
436 "need faulthandler.register")
Victor Stinnera01ca122011-04-01 12:56:17 +0200437 def check_register(self, filename=False, all_threads=False,
438 unregister=False):
Victor Stinner024e37a2011-03-31 01:31:06 +0200439 """
440 Register a handler displaying the traceback on a user signal. Raise the
441 signal and check the written traceback.
442
443 Raise an error if the output doesn't match the expected format.
444 """
Victor Stinnera01ca122011-04-01 12:56:17 +0200445 signum = signal.SIGUSR1
Victor Stinner024e37a2011-03-31 01:31:06 +0200446 code = """
447import faulthandler
448import os
449import signal
450
451def func(signum):
452 os.kill(os.getpid(), signum)
453
Victor Stinnera01ca122011-04-01 12:56:17 +0200454signum = {signum}
455unregister = {unregister}
Victor Stinner024e37a2011-03-31 01:31:06 +0200456if {has_filename}:
457 file = open({filename}, "wb")
458else:
459 file = None
460faulthandler.register(signum, file=file, all_threads={all_threads})
Victor Stinnera01ca122011-04-01 12:56:17 +0200461if unregister:
462 faulthandler.unregister(signum)
Victor Stinner024e37a2011-03-31 01:31:06 +0200463func(signum)
464if file is not None:
465 file.close()
466""".strip()
467 code = code.format(
468 filename=repr(filename),
469 has_filename=bool(filename),
470 all_threads=all_threads,
Victor Stinnera01ca122011-04-01 12:56:17 +0200471 signum=signum,
472 unregister=unregister,
Victor Stinner024e37a2011-03-31 01:31:06 +0200473 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200474 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200475 trace = '\n'.join(trace)
Victor Stinnera01ca122011-04-01 12:56:17 +0200476 if not unregister:
477 if all_threads:
478 regex = 'Current thread XXX:\n'
479 else:
480 regex = 'Traceback \(most recent call first\):\n'
481 regex = expected_traceback(6, 17, regex)
482 self.assertRegex(trace, regex)
Victor Stinner024e37a2011-03-31 01:31:06 +0200483 else:
Victor Stinnera01ca122011-04-01 12:56:17 +0200484 self.assertEqual(trace, '')
485 if unregister:
486 self.assertNotEqual(exitcode, 0)
487 else:
488 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200489
490 def test_register(self):
491 self.check_register()
492
Victor Stinnera01ca122011-04-01 12:56:17 +0200493 def test_unregister(self):
494 self.check_register(unregister=True)
495
Victor Stinner024e37a2011-03-31 01:31:06 +0200496 def test_register_file(self):
497 with temporary_filename() as filename:
498 self.check_register(filename=filename)
499
500 def test_register_threads(self):
501 self.check_register(all_threads=True)
502
503
504def test_main():
505 support.run_unittest(FaultHandlerTests)
506
507if __name__ == "__main__":
508 test_main()