blob: 1a79c5065613c14e3cd946dc8f2f675eed7fb158 [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
Victor Stinnerd727e232011-04-01 12:13:55 +0200115 def test_sigabrt(self):
116 self.check_fatal_error("""
117import faulthandler
118faulthandler.enable()
119faulthandler._sigabrt()
120""".strip(),
121 3,
122 'Aborted')
123
Victor Stinner024e37a2011-03-31 01:31:06 +0200124 @unittest.skipIf(sys.platform == 'win32',
125 "SIGFPE cannot be caught on Windows")
126 def test_sigfpe(self):
127 self.check_fatal_error("""
128import faulthandler
129faulthandler.enable()
130faulthandler._sigfpe()
131""".strip(),
132 3,
133 'Floating point exception')
134
135 @unittest.skipIf(not hasattr(faulthandler, '_sigbus'),
136 "need faulthandler._sigbus()")
137 def test_sigbus(self):
138 self.check_fatal_error("""
139import faulthandler
140faulthandler.enable()
141faulthandler._sigbus()
142""".strip(),
143 3,
144 'Bus error')
145
146 @unittest.skipIf(not hasattr(faulthandler, '_sigill'),
147 "need faulthandler._sigill()")
148 def test_sigill(self):
149 self.check_fatal_error("""
150import faulthandler
151faulthandler.enable()
152faulthandler._sigill()
153""".strip(),
154 3,
155 'Illegal instruction')
156
157 def test_fatal_error(self):
158 self.check_fatal_error("""
159import faulthandler
160faulthandler._fatal_error(b'xyz')
161""".strip(),
162 2,
163 'xyz')
164
Victor Stinner024e37a2011-03-31 01:31:06 +0200165 @unittest.skipIf(not hasattr(faulthandler, '_stack_overflow'),
166 'need faulthandler._stack_overflow()')
167 def test_stack_overflow(self):
168 self.check_fatal_error("""
169import faulthandler
170faulthandler.enable()
171faulthandler._stack_overflow()
172""".strip(),
173 3,
Victor Stinnerf0480752011-03-31 11:34:08 +0200174 '(?:Segmentation fault|Bus error)',
175 other_regex='unable to raise a stack overflow')
Victor Stinner024e37a2011-03-31 01:31:06 +0200176
177 def test_gil_released(self):
178 self.check_fatal_error("""
179import faulthandler
180faulthandler.enable()
181faulthandler._read_null(True)
182""".strip(),
183 3,
184 '(?:Segmentation fault|Bus error)')
185
186 def test_enable_file(self):
187 with temporary_filename() as filename:
188 self.check_fatal_error("""
189import faulthandler
190output = open({filename}, 'wb')
191faulthandler.enable(output)
192faulthandler._read_null(True)
193""".strip().format(filename=repr(filename)),
194 4,
195 '(?:Segmentation fault|Bus error)',
196 filename=filename)
197
198 def test_enable_threads(self):
199 self.check_fatal_error("""
200import faulthandler
201faulthandler.enable(all_threads=True)
202faulthandler._read_null(True)
203""".strip(),
204 3,
205 '(?:Segmentation fault|Bus error)',
206 all_threads=True)
207
208 def test_disable(self):
209 code = """
210import faulthandler
211faulthandler.enable()
212faulthandler.disable()
213faulthandler._read_null()
214""".strip()
215 not_expected = 'Fatal Python error'
Victor Stinner05585cb2011-03-31 13:29:56 +0200216 stderr, exitcode = self.get_output(code)
Victor Stinner024e37a2011-03-31 01:31:06 +0200217 stder = '\n'.join(stderr)
218 self.assertTrue(not_expected not in stderr,
219 "%r is present in %r" % (not_expected, stderr))
Victor Stinner05585cb2011-03-31 13:29:56 +0200220 self.assertNotEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200221
222 def test_is_enabled(self):
223 was_enabled = faulthandler.is_enabled()
224 try:
225 faulthandler.enable()
226 self.assertTrue(faulthandler.is_enabled())
227 faulthandler.disable()
228 self.assertFalse(faulthandler.is_enabled())
229 finally:
230 if was_enabled:
231 faulthandler.enable()
232 else:
233 faulthandler.disable()
234
235 def check_dump_traceback(self, filename):
236 """
237 Explicitly call dump_traceback() function and check its output.
238 Raise an error if the output doesn't match the expected format.
239 """
240 code = """
241import faulthandler
242
243def funcB():
244 if {has_filename}:
245 with open({filename}, "wb") as fp:
246 faulthandler.dump_traceback(fp)
247 else:
248 faulthandler.dump_traceback()
249
250def funcA():
251 funcB()
252
253funcA()
254""".strip()
255 code = code.format(
256 filename=repr(filename),
257 has_filename=bool(filename),
258 )
259 if filename:
260 lineno = 6
261 else:
262 lineno = 8
263 expected = [
264 'Traceback (most recent call first):',
265 ' File "<string>", line %s in funcB' % lineno,
266 ' File "<string>", line 11 in funcA',
267 ' File "<string>", line 13 in <module>'
268 ]
Victor Stinner05585cb2011-03-31 13:29:56 +0200269 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200270 self.assertEqual(trace, expected)
Victor Stinner05585cb2011-03-31 13:29:56 +0200271 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200272
273 def test_dump_traceback(self):
274 self.check_dump_traceback(None)
Victor Stinner19402332011-03-31 18:15:52 +0200275
276 def test_dump_traceback_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200277 with temporary_filename() as filename:
278 self.check_dump_traceback(filename)
279
280 def check_dump_traceback_threads(self, filename):
281 """
282 Call explicitly dump_traceback(all_threads=True) and check the output.
283 Raise an error if the output doesn't match the expected format.
284 """
285 code = """
286import faulthandler
287from threading import Thread, Event
288import time
289
290def dump():
291 if {filename}:
292 with open({filename}, "wb") as fp:
293 faulthandler.dump_traceback(fp, all_threads=True)
294 else:
295 faulthandler.dump_traceback(all_threads=True)
296
297class Waiter(Thread):
298 # avoid blocking if the main thread raises an exception.
299 daemon = True
300
301 def __init__(self):
302 Thread.__init__(self)
303 self.running = Event()
304 self.stop = Event()
305
306 def run(self):
307 self.running.set()
308 self.stop.wait()
309
310waiter = Waiter()
311waiter.start()
312waiter.running.wait()
313dump()
314waiter.stop.set()
315waiter.join()
316""".strip()
317 code = code.format(filename=repr(filename))
Victor Stinner05585cb2011-03-31 13:29:56 +0200318 output, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200319 output = '\n'.join(output)
320 if filename:
321 lineno = 8
322 else:
323 lineno = 10
324 regex = """
325^Thread 0x[0-9a-f]+:
326(?: File ".*threading.py", line [0-9]+ in wait
327)? File ".*threading.py", line [0-9]+ in wait
328 File "<string>", line 23 in run
329 File ".*threading.py", line [0-9]+ in _bootstrap_inner
330 File ".*threading.py", line [0-9]+ in _bootstrap
331
332Current thread XXX:
333 File "<string>", line {lineno} in dump
334 File "<string>", line 28 in <module>$
335""".strip()
336 regex = regex.format(lineno=lineno)
337 self.assertRegex(output, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200338 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200339
340 def test_dump_traceback_threads(self):
341 self.check_dump_traceback_threads(None)
Victor Stinner19402332011-03-31 18:15:52 +0200342
343 def test_dump_traceback_threads_file(self):
Victor Stinner024e37a2011-03-31 01:31:06 +0200344 with temporary_filename() as filename:
345 self.check_dump_traceback_threads(filename)
346
347 def _check_dump_tracebacks_later(self, repeat, cancel, filename):
348 """
349 Check how many times the traceback is written in timeout x 2.5 seconds,
350 or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending
351 on repeat and cancel options.
352
353 Raise an error if the output doesn't match the expect format.
354 """
355 code = """
356import faulthandler
357import time
358
359def func(repeat, cancel, timeout):
360 pause = timeout * 2.5
Victor Stinner7ad24e92011-03-31 22:35:49 +0200361 # on Windows XP, b-a gives 1.249931 after sleep(1.25)
362 min_pause = pause * 0.9
Victor Stinner024e37a2011-03-31 01:31:06 +0200363 a = time.time()
364 time.sleep(pause)
365 faulthandler.cancel_dump_tracebacks_later()
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
370 if cancel:
371 pause = timeout * 1.5
Victor Stinner7ad24e92011-03-31 22:35:49 +0200372 min_pause = pause * 0.9
Victor Stinner024e37a2011-03-31 01:31:06 +0200373 a = time.time()
374 time.sleep(pause)
375 b = time.time()
376 # Check that sleep() was not interrupted
Victor Stinner7ad24e92011-03-31 22:35:49 +0200377 assert (b - a) >= min_pause, "{{}} < {{}}".format(b - a, min_pause)
Victor Stinner024e37a2011-03-31 01:31:06 +0200378
379timeout = 0.5
380repeat = {repeat}
381cancel = {cancel}
382if {has_filename}:
383 file = open({filename}, "wb")
384else:
385 file = None
386faulthandler.dump_tracebacks_later(timeout,
387 repeat=repeat, file=file)
388func(repeat, cancel, timeout)
389if file is not None:
390 file.close()
391""".strip()
392 code = code.format(
393 filename=repr(filename),
394 has_filename=bool(filename),
395 repeat=repeat,
396 cancel=cancel,
397 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200398 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200399 trace = '\n'.join(trace)
400
401 if repeat:
402 count = 2
403 else:
404 count = 1
405 header = 'Thread 0x[0-9a-f]+:\n'
Victor Stinner7ad24e92011-03-31 22:35:49 +0200406 regex = expected_traceback(9, 33, header, count=count)
407 self.assertRegex(trace, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200408 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200409
410 @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'),
411 'need faulthandler.dump_tracebacks_later()')
412 def check_dump_tracebacks_later(self, repeat=False, cancel=False,
413 file=False):
414 if file:
415 with temporary_filename() as filename:
416 self._check_dump_tracebacks_later(repeat, cancel, filename)
417 else:
418 self._check_dump_tracebacks_later(repeat, cancel, None)
419
420 def test_dump_tracebacks_later(self):
421 self.check_dump_tracebacks_later()
422
423 def test_dump_tracebacks_later_repeat(self):
424 self.check_dump_tracebacks_later(repeat=True)
425
426 def test_dump_tracebacks_later_repeat_cancel(self):
427 self.check_dump_tracebacks_later(repeat=True, cancel=True)
428
429 def test_dump_tracebacks_later_file(self):
430 self.check_dump_tracebacks_later(file=True)
431
432 @unittest.skipIf(not hasattr(faulthandler, "register"),
433 "need faulthandler.register")
434 def check_register(self, filename=False, all_threads=False):
435 """
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 """
441 code = """
442import faulthandler
443import os
444import signal
445
446def func(signum):
447 os.kill(os.getpid(), signum)
448
449signum = signal.SIGUSR1
450if {has_filename}:
451 file = open({filename}, "wb")
452else:
453 file = None
454faulthandler.register(signum, file=file, all_threads={all_threads})
455func(signum)
456if file is not None:
457 file.close()
458""".strip()
459 code = code.format(
460 filename=repr(filename),
461 has_filename=bool(filename),
462 all_threads=all_threads,
463 )
Victor Stinner05585cb2011-03-31 13:29:56 +0200464 trace, exitcode = self.get_output(code, filename)
Victor Stinner024e37a2011-03-31 01:31:06 +0200465 trace = '\n'.join(trace)
466 if all_threads:
467 regex = 'Current thread XXX:\n'
468 else:
469 regex = 'Traceback \(most recent call first\):\n'
470 regex = expected_traceback(6, 14, regex)
Victor Stinner7ad24e92011-03-31 22:35:49 +0200471 self.assertRegex(trace, regex)
Victor Stinner05585cb2011-03-31 13:29:56 +0200472 self.assertEqual(exitcode, 0)
Victor Stinner024e37a2011-03-31 01:31:06 +0200473
474 def test_register(self):
475 self.check_register()
476
477 def test_register_file(self):
478 with temporary_filename() as filename:
479 self.check_register(filename=filename)
480
481 def test_register_threads(self):
482 self.check_register(all_threads=True)
483
484
485def test_main():
486 support.run_unittest(FaultHandlerTests)
487
488if __name__ == "__main__":
489 test_main()