blob: f26269c7bd47b00918ce9a4f85d3e6fae0d20612 [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
11try:
12 from resource import setrlimit, RLIMIT_CORE, error as resource_error
13except ImportError:
14 prepare_subprocess = None
15else:
16 def prepare_subprocess():
17 # don't create core file
18 try:
19 setrlimit(RLIMIT_CORE, (0, 0))
20 except (ValueError, resource_error):
21 pass
22
23def expected_traceback(lineno1, lineno2, header, count=1):
24 regex = header
Victor Stinner7ad24e92011-03-31 22:35:49 +020025 regex += ' File "<string>", line %s in func\n' % lineno1
26 regex += ' File "<string>", line %s in <module>' % lineno2
Victor Stinner024e37a2011-03-31 01:31:06 +020027 if count != 1:
28 regex = (regex + '\n') * (count - 1) + regex
29 return '^' + regex + '$'
30
31@contextmanager
32def temporary_filename():
33 filename = tempfile.mktemp()
34 try:
35 yield filename
36 finally:
37 support.unlink(filename)
38
39class FaultHandlerTests(unittest.TestCase):
Victor Stinner05585cb2011-03-31 13:29:56 +020040 def get_output(self, code, filename=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020041 """
42 Run the specified code in Python (in a new child process) and read the
43 output from the standard error or from a file (if filename is set).
44 Return the output lines as a list.
45
46 Strip the reference count from the standard error for Python debug
47 build, and replace "Current thread 0x00007f8d8fbd9700" by "Current
48 thread XXX".
49 """
50 options = {}
51 if prepare_subprocess:
52 options['preexec_fn'] = prepare_subprocess
53 process = script_helper.spawn_python('-c', code, **options)
54 stdout, stderr = process.communicate()
55 exitcode = process.wait()
Victor Stinner19402332011-03-31 18:15:52 +020056 output = support.strip_python_stderr(stdout)
57 output = output.decode('ascii', 'backslashreplace')
Victor Stinner024e37a2011-03-31 01:31:06 +020058 if filename:
Victor Stinner19402332011-03-31 18:15:52 +020059 self.assertEqual(output, '')
Victor Stinner024e37a2011-03-31 01:31:06 +020060 with open(filename, "rb") as fp:
61 output = fp.read()
Victor Stinner19402332011-03-31 18:15:52 +020062 output = output.decode('ascii', 'backslashreplace')
Victor Stinner024e37a2011-03-31 01:31:06 +020063 output = re.sub('Current thread 0x[0-9a-f]+',
64 'Current thread XXX',
65 output)
Victor Stinner05585cb2011-03-31 13:29:56 +020066 return output.splitlines(), exitcode
Victor Stinner024e37a2011-03-31 01:31:06 +020067
68 def check_fatal_error(self, code, line_number, name_regex,
Victor Stinnerf0480752011-03-31 11:34:08 +020069 filename=None, all_threads=False, other_regex=None):
Victor Stinner024e37a2011-03-31 01:31:06 +020070 """
71 Check that the fault handler for fatal errors is enabled and check the
72 traceback from the child process output.
73
74 Raise an error if the output doesn't match the expected format.
75 """
76 if all_threads:
77 header = 'Current thread XXX'
78 else:
79 header = 'Traceback (most recent call first)'
80 regex = """
81^Fatal Python error: {name}
82
83{header}:
84 File "<string>", line {lineno} in <module>$
85""".strip()
86 regex = regex.format(
87 lineno=line_number,
88 name=name_regex,
89 header=re.escape(header))
Victor Stinnerf0480752011-03-31 11:34:08 +020090 if other_regex:
91 regex += '|' + other_regex
Victor Stinner05585cb2011-03-31 13:29:56 +020092 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +020093 output = '\n'.join(output)
94 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +020095 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +020096
97 def test_read_null(self):
98 self.check_fatal_error("""
99import faulthandler
100faulthandler.enable()
101faulthandler._read_null()
102""".strip(),
103 3,
104 '(?:Segmentation fault|Bus error)')
105
106 def test_sigsegv(self):
107 self.check_fatal_error("""
108import faulthandler
109faulthandler.enable()
110faulthandler._sigsegv()
111""".strip(),
112 3,
113 'Segmentation fault')
114
115 @unittest.skipIf(sys.platform == 'win32',
116 "SIGFPE cannot be caught on Windows")
117 def test_sigfpe(self):
118 self.check_fatal_error("""
119import faulthandler
120faulthandler.enable()
121faulthandler._sigfpe()
122""".strip(),
123 3,
124 'Floating point exception')
125
126 @unittest.skipIf(not hasattr(faulthandler, '_sigbus'),
127 "need faulthandler._sigbus()")
128 def test_sigbus(self):
129 self.check_fatal_error("""
130import faulthandler
131faulthandler.enable()
132faulthandler._sigbus()
133""".strip(),
134 3,
135 'Bus error')
136
137 @unittest.skipIf(not hasattr(faulthandler, '_sigill'),
138 "need faulthandler._sigill()")
139 def test_sigill(self):
140 self.check_fatal_error("""
141import faulthandler
142faulthandler.enable()
143faulthandler._sigill()
144""".strip(),
145 3,
146 'Illegal instruction')
147
148 def test_fatal_error(self):
149 self.check_fatal_error("""
150import faulthandler
151faulthandler._fatal_error(b'xyz')
152""".strip(),
153 2,
154 'xyz')
155
Victor Stinner024e37a2011-03-31 01:31:06 +0200156 @unittest.skipIf(not hasattr(faulthandler, '_stack_overflow'),
157 'need faulthandler._stack_overflow()')
158 def test_stack_overflow(self):
159 self.check_fatal_error("""
160import faulthandler
161faulthandler.enable()
162faulthandler._stack_overflow()
163""".strip(),
164 3,
Victor Stinnerf0480752011-03-31 11:34:08 +0200165 '(?:Segmentation fault|Bus error)',
166 other_regex='unable to raise a stack overflow')
Victor Stinner024e37a2011-03-31 01:31:06 +0200167
168 def test_gil_released(self):
169 self.check_fatal_error("""
170import faulthandler
171faulthandler.enable()
172faulthandler._read_null(True)
173""".strip(),
174 3,
175 '(?:Segmentation fault|Bus error)')
176
177 def test_enable_file(self):
178 with temporary_filename() as filename:
179 self.check_fatal_error("""
180import faulthandler
181output = open({filename}, 'wb')
182faulthandler.enable(output)
183faulthandler._read_null(True)
184""".strip().format(filename=repr(filename)),
185 4,
186 '(?:Segmentation fault|Bus error)',
187 filename=filename)
188
189 def test_enable_threads(self):
190 self.check_fatal_error("""
191import faulthandler
192faulthandler.enable(all_threads=True)
193faulthandler._read_null(True)
194""".strip(),
195 3,
196 '(?:Segmentation fault|Bus error)',
197 all_threads=True)
198
199 def test_disable(self):
200 code = """
201import faulthandler
202faulthandler.enable()
203faulthandler.disable()
204faulthandler._read_null()
205""".strip()
206 not_expected = 'Fatal Python error'
Victor Stinner05585cb2011-03-31 13:29:56 +0200207 stderr, exitcode = self.get_output(code)
Victor Stinner024e37a2011-03-31 01:31:06 +0200208 stder = '\n'.join(stderr)
209 self.assertTrue(not_expected not in stderr,
210 "%r is present in %r" % (not_expected, stderr))
Victor Stinner05585cb2011-03-31 13:29:56 +0200211 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200212
213 def test_is_enabled(self):
214 was_enabled = faulthandler.is_enabled()
215 try:
216 faulthandler.enable()
217 self.assertTrue(faulthandler.is_enabled())
218 faulthandler.disable()
219 self.assertFalse(faulthandler.is_enabled())
220 finally:
221 if was_enabled:
222 faulthandler.enable()
223 else:
224 faulthandler.disable()
225
226 def check_dump_traceback(self, filename):
227 """
228 Explicitly call dump_traceback() function and check its output.
229 Raise an error if the output doesn't match the expected format.
230 """
231 code = """
232import faulthandler
233
234def funcB():
235 if {has_filename}:
236 with open({filename}, "wb") as fp:
237 faulthandler.dump_traceback(fp)
238 else:
239 faulthandler.dump_traceback()
240
241def funcA():
242 funcB()
243
244funcA()
245""".strip()
246 code = code.format(
247 filename=repr(filename),
248 has_filename=bool(filename),
249 )
250 if filename:
251 lineno = 6
252 else:
253 lineno = 8
254 expected = [
255 'Traceback (most recent call first):',
256 ' File "<string>", line %s in funcB' % lineno,
257 ' File "<string>", line 11 in funcA',
258 ' File "<string>", line 13 in <module>'
259 ]
Victor Stinner05585cb2011-03-31 13:29:56 +0200260 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200261 self.assertEqual(trace, expected)
Victor Stinner05585cb2011-03-31 13:29:56 +0200262 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200263
264 def test_dump_traceback(self):
265 self.check_dump_traceback(None)
Victor Stinner19402332011-03-31 18:15:52 +0200266
267 def test_dump_traceback_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200268 with temporary_filename() as filename:
269 self.check_dump_traceback(filename)
270
271 def check_dump_traceback_threads(self, filename):
272 """
273 Call explicitly dump_traceback(all_threads=True) and check the output.
274 Raise an error if the output doesn't match the expected format.
275 """
276 code = """
277import faulthandler
278from threading import Thread, Event
279import time
280
281def dump():
282 if {filename}:
283 with open({filename}, "wb") as fp:
284 faulthandler.dump_traceback(fp, all_threads=True)
285 else:
286 faulthandler.dump_traceback(all_threads=True)
287
288class Waiter(Thread):
289 # avoid blocking if the main thread raises an exception.
290 daemon = True
291
292 def __init__(self):
293 Thread.__init__(self)
294 self.running = Event()
295 self.stop = Event()
296
297 def run(self):
298 self.running.set()
299 self.stop.wait()
300
301waiter = Waiter()
302waiter.start()
303waiter.running.wait()
304dump()
305waiter.stop.set()
306waiter.join()
307""".strip()
308 code = code.format(filename=repr(filename))
Victor Stinner05585cb2011-03-31 13:29:56 +0200309 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200310 output = '\n'.join(output)
311 if filename:
312 lineno = 8
313 else:
314 lineno = 10
315 regex = """
316^Thread 0x[0-9a-f]+:
317(?: File ".*threading.py", line [0-9]+ in wait
318)? File ".*threading.py", line [0-9]+ in wait
319 File "<string>", line 23 in run
320 File ".*threading.py", line [0-9]+ in _bootstrap_inner
321 File ".*threading.py", line [0-9]+ in _bootstrap
322
323Current thread XXX:
324 File "<string>", line {lineno} in dump
325 File "<string>", line 28 in <module>$
326""".strip()
327 regex = regex.format(lineno=lineno)
328 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200329 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200330
331 def test_dump_traceback_threads(self):
332 self.check_dump_traceback_threads(None)
Victor Stinner19402332011-03-31 18:15:52 +0200333
334 def test_dump_traceback_threads_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200335 with temporary_filename() as filename:
336 self.check_dump_traceback_threads(filename)
337
338 def _check_dump_tracebacks_later(self, repeat, cancel, filename):
339 """
340 Check how many times the traceback is written in timeout x 2.5 seconds,
341 or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending
342 on repeat and cancel options.
343
344 Raise an error if the output doesn't match the expect format.
345 """
346 code = """
347import faulthandler
348import time
349
350def func(repeat, cancel, timeout):
351 pause = timeout * 2.5
Victor Stinner7ad24e92011-03-31 22:35:49 +0200352 # on Windows XP, b-a gives 1.249931 after sleep(1.25)
353 min_pause = pause * 0.9
Victor Stinner024e37a2011-03-31 01:31:06 +0200354 a = time.time()
355 time.sleep(pause)
356 faulthandler.cancel_dump_tracebacks_later()
357 b = time.time()
358 # Check that sleep() was not interrupted
Victor Stinner7ad24e92011-03-31 22:35:49 +0200359 assert (b - a) >= min_pause, "{{}} < {{}}".format(b - a, min_pause)
Victor Stinner024e37a2011-03-31 01:31:06 +0200360
361 if cancel:
362 pause = timeout * 1.5
Victor Stinner7ad24e92011-03-31 22:35:49 +0200363 min_pause = pause * 0.9
Victor Stinner024e37a2011-03-31 01:31:06 +0200364 a = time.time()
365 time.sleep(pause)
366 b = time.time()
367 # Check that sleep() was not interrupted
Victor Stinner7ad24e92011-03-31 22:35:49 +0200368 assert (b - a) >= min_pause, "{{}} < {{}}".format(b - a, min_pause)
Victor Stinner024e37a2011-03-31 01:31:06 +0200369
370timeout = 0.5
371repeat = {repeat}
372cancel = {cancel}
373if {has_filename}:
374 file = open({filename}, "wb")
375else:
376 file = None
377faulthandler.dump_tracebacks_later(timeout,
378 repeat=repeat, file=file)
379func(repeat, cancel, timeout)
380if file is not None:
381 file.close()
382""".strip()
383 code = code.format(
384 filename=repr(filename),
385 has_filename=bool(filename),
386 repeat=repeat,
387 cancel=cancel,
388 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200389 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200390 trace = '\n'.join(trace)
391
392 if repeat:
393 count = 2
394 else:
395 count = 1
396 header = 'Thread 0x[0-9a-f]+:\n'
Victor Stinner7ad24e92011-03-31 22:35:49 +0200397 regex = expected_traceback(9, 33, header, count=count)
398 self.assertRegex(trace, regex)
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
417 def test_dump_tracebacks_later_repeat_cancel(self):
418 self.check_dump_tracebacks_later(repeat=True, cancel=True)
419
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")
425 def check_register(self, filename=False, all_threads=False):
426 """
427 Register a handler displaying the traceback on a user signal. Raise the
428 signal and check the written traceback.
429
430 Raise an error if the output doesn't match the expected format.
431 """
432 code = """
433import faulthandler
434import os
435import signal
436
437def func(signum):
438 os.kill(os.getpid(), signum)
439
440signum = signal.SIGUSR1
441if {has_filename}:
442 file = open({filename}, "wb")
443else:
444 file = None
445faulthandler.register(signum, file=file, all_threads={all_threads})
446func(signum)
447if file is not None:
448 file.close()
449""".strip()
450 code = code.format(
451 filename=repr(filename),
452 has_filename=bool(filename),
453 all_threads=all_threads,
454 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200455 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200456 trace = '\n'.join(trace)
457 if all_threads:
458 regex = 'Current thread XXX:\n'
459 else:
460 regex = 'Traceback \(most recent call first\):\n'
461 regex = expected_traceback(6, 14, regex)
Victor Stinner7ad24e92011-03-31 22:35:49 +0200462 self.assertRegex(trace, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200463 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200464
465 def test_register(self):
466 self.check_register()
467
468 def test_register_file(self):
469 with temporary_filename() as filename:
470 self.check_register(filename=filename)
471
472 def test_register_threads(self):
473 self.check_register(all_threads=True)
474
475
476def test_main():
477 support.run_unittest(FaultHandlerTests)
478
479if __name__ == "__main__":
480 test_main()