blob: 1a3f5e240045333d039745371e8e816f3cc02296 [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
25 regex += r' File "\<string\>", line %s in func\n' % lineno1
26 regex += r' File "\<string\>", line %s in \<module\>' % lineno2
27 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
352 a = time.time()
353 time.sleep(pause)
354 faulthandler.cancel_dump_tracebacks_later()
355 b = time.time()
356 # Check that sleep() was not interrupted
357 assert (b -a) >= pause
358
359 if cancel:
360 pause = timeout * 1.5
361 a = time.time()
362 time.sleep(pause)
363 b = time.time()
364 # Check that sleep() was not interrupted
365 assert (b -a) >= pause
366
367timeout = 0.5
368repeat = {repeat}
369cancel = {cancel}
370if {has_filename}:
371 file = open({filename}, "wb")
372else:
373 file = None
374faulthandler.dump_tracebacks_later(timeout,
375 repeat=repeat, file=file)
376func(repeat, cancel, timeout)
377if file is not None:
378 file.close()
379""".strip()
380 code = code.format(
381 filename=repr(filename),
382 has_filename=bool(filename),
383 repeat=repeat,
384 cancel=cancel,
385 )
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
389 if repeat:
390 count = 2
391 else:
392 count = 1
393 header = 'Thread 0x[0-9a-f]+:\n'
394 regex = expected_traceback(7, 30, header, count=count)
395 self.assertRegex(trace, '^%s$' % regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200396 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200397
398 @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
399 'need faulthandler.dump_tracebacks_later()')
400 def check_dump_tracebacks_later(self, repeat=False, cancel=False,
401 file=False):
402 if file:
403 with temporary_filename() as filename:
404 self._check_dump_tracebacks_later(repeat, cancel, filename)
405 else:
406 self._check_dump_tracebacks_later(repeat, cancel, None)
407
408 def test_dump_tracebacks_later(self):
409 self.check_dump_tracebacks_later()
410
411 def test_dump_tracebacks_later_repeat(self):
412 self.check_dump_tracebacks_later(repeat=True)
413
414 def test_dump_tracebacks_later_repeat_cancel(self):
415 self.check_dump_tracebacks_later(repeat=True, cancel=True)
416
417 def test_dump_tracebacks_later_file(self):
418 self.check_dump_tracebacks_later(file=True)
419
420 @unittest.skipIf(not hasattr(faulthandler, "register"),
421 "need faulthandler.register")
422 def check_register(self, filename=False, all_threads=False):
423 """
424 Register a handler displaying the traceback on a user signal. Raise the
425 signal and check the written traceback.
426
427 Raise an error if the output doesn't match the expected format.
428 """
429 code = """
430import faulthandler
431import os
432import signal
433
434def func(signum):
435 os.kill(os.getpid(), signum)
436
437signum = signal.SIGUSR1
438if {has_filename}:
439 file = open({filename}, "wb")
440else:
441 file = None
442faulthandler.register(signum, file=file, all_threads={all_threads})
443func(signum)
444if file is not None:
445 file.close()
446""".strip()
447 code = code.format(
448 filename=repr(filename),
449 has_filename=bool(filename),
450 all_threads=all_threads,
451 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200452 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200453 trace = '\n'.join(trace)
454 if all_threads:
455 regex = 'Current thread XXX:\n'
456 else:
457 regex = 'Traceback \(most recent call first\):\n'
458 regex = expected_traceback(6, 14, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200459 self.assertRegex(trace, '^%s$' % regex)
460 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200461
462 def test_register(self):
463 self.check_register()
464
465 def test_register_file(self):
466 with temporary_filename() as filename:
467 self.check_register(filename=filename)
468
469 def test_register_threads(self):
470 self.check_register(all_threads=True)
471
472
473def test_main():
474 support.run_unittest(FaultHandlerTests)
475
476if __name__ == "__main__":
477 test_main()