| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | from contextlib import contextmanager | 
					
						
							| 
									
										
										
										
											2011-04-08 13:39:59 +02:00
										 |  |  | import datetime | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | import faulthandler | 
					
						
							| 
									
										
										
										
											2011-06-29 13:44:05 +02:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | import re | 
					
						
							|  |  |  | import signal | 
					
						
							|  |  |  | import subprocess | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | from test import support, script_helper | 
					
						
							|  |  |  | import tempfile | 
					
						
							|  |  |  | import unittest | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-04-07 11:50:25 +02:00
										 |  |  | try: | 
					
						
							|  |  |  |     import threading | 
					
						
							|  |  |  |     HAVE_THREADS = True | 
					
						
							|  |  |  | except ImportError: | 
					
						
							|  |  |  |     HAVE_THREADS = False | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-04-01 15:37:12 +02:00
										 |  |  | TIMEOUT = 0.5 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | try: | 
					
						
							|  |  |  |     from resource import setrlimit, RLIMIT_CORE, error as resource_error | 
					
						
							|  |  |  | except ImportError: | 
					
						
							|  |  |  |     prepare_subprocess = None | 
					
						
							|  |  |  | else: | 
					
						
							|  |  |  |     def prepare_subprocess(): | 
					
						
							|  |  |  |         # don't create core file | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             setrlimit(RLIMIT_CORE, (0, 0)) | 
					
						
							|  |  |  |         except (ValueError, resource_error): | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-06-01 13:49:12 +02:00
										 |  |  | def expected_traceback(lineno1, lineno2, header, min_count=1): | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |     regex = header | 
					
						
							| 
									
										
										
										
											2011-03-31 22:35:49 +02:00
										 |  |  |     regex += '  File "<string>", line %s in func\n' % lineno1 | 
					
						
							|  |  |  |     regex += '  File "<string>", line %s in <module>' % lineno2 | 
					
						
							| 
									
										
										
										
											2011-06-01 13:49:12 +02:00
										 |  |  |     if 1 < min_count: | 
					
						
							|  |  |  |         return '^' + (regex + '\n') * (min_count - 1) + regex | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         return '^' + regex + '$' | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | @contextmanager | 
					
						
							|  |  |  | def temporary_filename(): | 
					
						
							|  |  |  |     filename = tempfile.mktemp() | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |         yield filename | 
					
						
							|  |  |  |     finally: | 
					
						
							|  |  |  |         support.unlink(filename) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class FaultHandlerTests(unittest.TestCase): | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |     def get_output(self, code, filename=None): | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Run the specified code in Python (in a new child process) and read the | 
					
						
							|  |  |  |         output from the standard error or from a file (if filename is set). | 
					
						
							|  |  |  |         Return the output lines as a list. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Strip the reference count from the standard error for Python debug | 
					
						
							|  |  |  |         build, and replace "Current thread 0x00007f8d8fbd9700" by "Current | 
					
						
							|  |  |  |         thread XXX". | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         options = {} | 
					
						
							|  |  |  |         if prepare_subprocess: | 
					
						
							|  |  |  |             options['preexec_fn'] = prepare_subprocess | 
					
						
							|  |  |  |         process = script_helper.spawn_python('-c', code, **options) | 
					
						
							|  |  |  |         stdout, stderr = process.communicate() | 
					
						
							|  |  |  |         exitcode = process.wait() | 
					
						
							| 
									
										
										
										
											2011-03-31 18:15:52 +02:00
										 |  |  |         output = support.strip_python_stderr(stdout) | 
					
						
							|  |  |  |         output = output.decode('ascii', 'backslashreplace') | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         if filename: | 
					
						
							| 
									
										
										
										
											2011-03-31 18:15:52 +02:00
										 |  |  |             self.assertEqual(output, '') | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |             with open(filename, "rb") as fp: | 
					
						
							|  |  |  |                 output = fp.read() | 
					
						
							| 
									
										
										
										
											2011-03-31 18:15:52 +02:00
										 |  |  |             output = output.decode('ascii', 'backslashreplace') | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         output = re.sub('Current thread 0x[0-9a-f]+', | 
					
						
							|  |  |  |                         'Current thread XXX', | 
					
						
							|  |  |  |                         output) | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |         return output.splitlines(), exitcode | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def check_fatal_error(self, code, line_number, name_regex, | 
					
						
							| 
									
										
										
										
											2011-05-07 12:43:00 +02:00
										 |  |  |                           filename=None, all_threads=True, other_regex=None): | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Check that the fault handler for fatal errors is enabled and check the | 
					
						
							|  |  |  |         traceback from the child process output. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Raise an error if the output doesn't match the expected format. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         if all_threads: | 
					
						
							|  |  |  |             header = 'Current thread XXX' | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             header = 'Traceback (most recent call first)' | 
					
						
							|  |  |  |         regex = """
 | 
					
						
							|  |  |  | ^Fatal Python error: {name} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | {header}: | 
					
						
							|  |  |  |   File "<string>", line {lineno} in <module>$ | 
					
						
							|  |  |  | """.strip()
 | 
					
						
							|  |  |  |         regex = regex.format( | 
					
						
							|  |  |  |             lineno=line_number, | 
					
						
							|  |  |  |             name=name_regex, | 
					
						
							|  |  |  |             header=re.escape(header)) | 
					
						
							| 
									
										
										
										
											2011-03-31 11:34:08 +02:00
										 |  |  |         if other_regex: | 
					
						
							|  |  |  |             regex += '|' + other_regex | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |         output, exitcode = self.get_output(code, filename) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         output = '\n'.join(output) | 
					
						
							|  |  |  |         self.assertRegex(output, regex) | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |         self.assertNotEqual(exitcode, 0) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_read_null(self): | 
					
						
							|  |  |  |         self.check_fatal_error("""
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | faulthandler.enable() | 
					
						
							|  |  |  | faulthandler._read_null() | 
					
						
							|  |  |  | """.strip(),
 | 
					
						
							|  |  |  |             3, | 
					
						
							| 
									
										
										
										
											2011-08-08 22:43:45 +02:00
										 |  |  |             # Issue #12700: Read NULL raises SIGILL on Mac OS X Lion | 
					
						
							|  |  |  |             '(?:Segmentation fault|Bus error|Illegal instruction)') | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_sigsegv(self): | 
					
						
							|  |  |  |         self.check_fatal_error("""
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | faulthandler.enable() | 
					
						
							|  |  |  | faulthandler._sigsegv() | 
					
						
							|  |  |  | """.strip(),
 | 
					
						
							|  |  |  |             3, | 
					
						
							|  |  |  |             'Segmentation fault') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-04-01 12:13:55 +02:00
										 |  |  |     def test_sigabrt(self): | 
					
						
							|  |  |  |         self.check_fatal_error("""
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | faulthandler.enable() | 
					
						
							|  |  |  | faulthandler._sigabrt() | 
					
						
							|  |  |  | """.strip(),
 | 
					
						
							|  |  |  |             3, | 
					
						
							|  |  |  |             'Aborted') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |     @unittest.skipIf(sys.platform == 'win32', | 
					
						
							|  |  |  |                      "SIGFPE cannot be caught on Windows") | 
					
						
							|  |  |  |     def test_sigfpe(self): | 
					
						
							|  |  |  |         self.check_fatal_error("""
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | faulthandler.enable() | 
					
						
							|  |  |  | faulthandler._sigfpe() | 
					
						
							|  |  |  | """.strip(),
 | 
					
						
							|  |  |  |             3, | 
					
						
							|  |  |  |             'Floating point exception') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @unittest.skipIf(not hasattr(faulthandler, '_sigbus'), | 
					
						
							|  |  |  |                      "need faulthandler._sigbus()") | 
					
						
							|  |  |  |     def test_sigbus(self): | 
					
						
							|  |  |  |         self.check_fatal_error("""
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | faulthandler.enable() | 
					
						
							|  |  |  | faulthandler._sigbus() | 
					
						
							|  |  |  | """.strip(),
 | 
					
						
							|  |  |  |             3, | 
					
						
							|  |  |  |             'Bus error') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @unittest.skipIf(not hasattr(faulthandler, '_sigill'), | 
					
						
							|  |  |  |                      "need faulthandler._sigill()") | 
					
						
							|  |  |  |     def test_sigill(self): | 
					
						
							|  |  |  |         self.check_fatal_error("""
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | faulthandler.enable() | 
					
						
							|  |  |  | faulthandler._sigill() | 
					
						
							|  |  |  | """.strip(),
 | 
					
						
							|  |  |  |             3, | 
					
						
							|  |  |  |             'Illegal instruction') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_fatal_error(self): | 
					
						
							|  |  |  |         self.check_fatal_error("""
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | faulthandler._fatal_error(b'xyz') | 
					
						
							|  |  |  | """.strip(),
 | 
					
						
							|  |  |  |             2, | 
					
						
							|  |  |  |             'xyz') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @unittest.skipIf(not hasattr(faulthandler, '_stack_overflow'), | 
					
						
							|  |  |  |                      'need faulthandler._stack_overflow()') | 
					
						
							|  |  |  |     def test_stack_overflow(self): | 
					
						
							|  |  |  |         self.check_fatal_error("""
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | faulthandler.enable() | 
					
						
							|  |  |  | faulthandler._stack_overflow() | 
					
						
							|  |  |  | """.strip(),
 | 
					
						
							|  |  |  |             3, | 
					
						
							| 
									
										
										
										
											2011-03-31 11:34:08 +02:00
										 |  |  |             '(?:Segmentation fault|Bus error)', | 
					
						
							|  |  |  |             other_regex='unable to raise a stack overflow') | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_gil_released(self): | 
					
						
							|  |  |  |         self.check_fatal_error("""
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | faulthandler.enable() | 
					
						
							|  |  |  | faulthandler._read_null(True) | 
					
						
							|  |  |  | """.strip(),
 | 
					
						
							|  |  |  |             3, | 
					
						
							| 
									
										
										
										
											2011-08-08 22:43:45 +02:00
										 |  |  |             '(?:Segmentation fault|Bus error|Illegal instruction)') | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_enable_file(self): | 
					
						
							|  |  |  |         with temporary_filename() as filename: | 
					
						
							|  |  |  |             self.check_fatal_error("""
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | output = open({filename}, 'wb') | 
					
						
							|  |  |  | faulthandler.enable(output) | 
					
						
							| 
									
										
										
										
											2011-04-01 15:37:12 +02:00
										 |  |  | faulthandler._read_null() | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | """.strip().format(filename=repr(filename)),
 | 
					
						
							|  |  |  |                 4, | 
					
						
							| 
									
										
										
										
											2011-08-08 22:43:45 +02:00
										 |  |  |                 '(?:Segmentation fault|Bus error|Illegal instruction)', | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |                 filename=filename) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-05-07 12:43:00 +02:00
										 |  |  |     def test_enable_single_thread(self): | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         self.check_fatal_error("""
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							| 
									
										
										
										
											2011-05-07 12:43:00 +02:00
										 |  |  | faulthandler.enable(all_threads=False) | 
					
						
							| 
									
										
										
										
											2011-04-01 15:37:12 +02:00
										 |  |  | faulthandler._read_null() | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | """.strip(),
 | 
					
						
							|  |  |  |             3, | 
					
						
							| 
									
										
										
										
											2011-08-08 22:43:45 +02:00
										 |  |  |             '(?:Segmentation fault|Bus error|Illegal instruction)', | 
					
						
							| 
									
										
										
										
											2011-05-07 12:43:00 +02:00
										 |  |  |             all_threads=False) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_disable(self): | 
					
						
							|  |  |  |         code = """
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | faulthandler.enable() | 
					
						
							|  |  |  | faulthandler.disable() | 
					
						
							|  |  |  | faulthandler._read_null() | 
					
						
							|  |  |  | """.strip()
 | 
					
						
							|  |  |  |         not_expected = 'Fatal Python error' | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |         stderr, exitcode = self.get_output(code) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         stder = '\n'.join(stderr) | 
					
						
							|  |  |  |         self.assertTrue(not_expected not in stderr, | 
					
						
							|  |  |  |                      "%r is present in %r" % (not_expected, stderr)) | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |         self.assertNotEqual(exitcode, 0) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_is_enabled(self): | 
					
						
							| 
									
										
										
										
											2011-06-29 13:44:05 +02:00
										 |  |  |         orig_stderr = sys.stderr | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2011-06-29 13:44:05 +02:00
										 |  |  |             # regrtest may replace sys.stderr by io.StringIO object, but | 
					
						
							|  |  |  |             # faulthandler.enable() requires that sys.stderr has a fileno() | 
					
						
							|  |  |  |             # method | 
					
						
							| 
									
										
										
										
											2011-06-29 23:24:31 +02:00
										 |  |  |             sys.stderr = sys.__stderr__ | 
					
						
							| 
									
										
										
										
											2011-06-29 13:44:05 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             was_enabled = faulthandler.is_enabled() | 
					
						
							|  |  |  |             try: | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |                 faulthandler.enable() | 
					
						
							| 
									
										
										
										
											2011-06-29 13:44:05 +02:00
										 |  |  |                 self.assertTrue(faulthandler.is_enabled()) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |                 faulthandler.disable() | 
					
						
							| 
									
										
										
										
											2011-06-29 13:44:05 +02:00
										 |  |  |                 self.assertFalse(faulthandler.is_enabled()) | 
					
						
							|  |  |  |             finally: | 
					
						
							|  |  |  |                 if was_enabled: | 
					
						
							|  |  |  |                     faulthandler.enable() | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     faulthandler.disable() | 
					
						
							|  |  |  |         finally: | 
					
						
							|  |  |  |             sys.stderr = orig_stderr | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def check_dump_traceback(self, filename): | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Explicitly call dump_traceback() function and check its output. | 
					
						
							|  |  |  |         Raise an error if the output doesn't match the expected format. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         code = """
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def funcB(): | 
					
						
							|  |  |  |     if {has_filename}: | 
					
						
							|  |  |  |         with open({filename}, "wb") as fp: | 
					
						
							| 
									
										
										
										
											2011-05-07 12:43:00 +02:00
										 |  |  |             faulthandler.dump_traceback(fp, all_threads=False) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |     else: | 
					
						
							| 
									
										
										
										
											2011-05-07 12:43:00 +02:00
										 |  |  |         faulthandler.dump_traceback(all_threads=False) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | def funcA(): | 
					
						
							|  |  |  |     funcB() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | funcA() | 
					
						
							|  |  |  | """.strip()
 | 
					
						
							|  |  |  |         code = code.format( | 
					
						
							|  |  |  |             filename=repr(filename), | 
					
						
							|  |  |  |             has_filename=bool(filename), | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         if filename: | 
					
						
							|  |  |  |             lineno = 6 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             lineno = 8 | 
					
						
							|  |  |  |         expected = [ | 
					
						
							|  |  |  |             'Traceback (most recent call first):', | 
					
						
							|  |  |  |             '  File "<string>", line %s in funcB' % lineno, | 
					
						
							|  |  |  |             '  File "<string>", line 11 in funcA', | 
					
						
							|  |  |  |             '  File "<string>", line 13 in <module>' | 
					
						
							|  |  |  |         ] | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |         trace, exitcode = self.get_output(code, filename) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         self.assertEqual(trace, expected) | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |         self.assertEqual(exitcode, 0) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_dump_traceback(self): | 
					
						
							|  |  |  |         self.check_dump_traceback(None) | 
					
						
							| 
									
										
										
										
											2011-03-31 18:15:52 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_dump_traceback_file(self): | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         with temporary_filename() as filename: | 
					
						
							|  |  |  |             self.check_dump_traceback(filename) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-04-07 11:50:25 +02:00
										 |  |  |     @unittest.skipIf(not HAVE_THREADS, 'need threads') | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |     def check_dump_traceback_threads(self, filename): | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Call explicitly dump_traceback(all_threads=True) and check the output. | 
					
						
							|  |  |  |         Raise an error if the output doesn't match the expected format. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         code = """
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | from threading import Thread, Event | 
					
						
							|  |  |  | import time | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def dump(): | 
					
						
							|  |  |  |     if {filename}: | 
					
						
							|  |  |  |         with open({filename}, "wb") as fp: | 
					
						
							|  |  |  |             faulthandler.dump_traceback(fp, all_threads=True) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         faulthandler.dump_traceback(all_threads=True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Waiter(Thread): | 
					
						
							|  |  |  |     # avoid blocking if the main thread raises an exception. | 
					
						
							|  |  |  |     daemon = True | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self): | 
					
						
							|  |  |  |         Thread.__init__(self) | 
					
						
							|  |  |  |         self.running = Event() | 
					
						
							|  |  |  |         self.stop = Event() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def run(self): | 
					
						
							|  |  |  |         self.running.set() | 
					
						
							|  |  |  |         self.stop.wait() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | waiter = Waiter() | 
					
						
							|  |  |  | waiter.start() | 
					
						
							|  |  |  | waiter.running.wait() | 
					
						
							|  |  |  | dump() | 
					
						
							|  |  |  | waiter.stop.set() | 
					
						
							|  |  |  | waiter.join() | 
					
						
							|  |  |  | """.strip()
 | 
					
						
							|  |  |  |         code = code.format(filename=repr(filename)) | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |         output, exitcode = self.get_output(code, filename) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         output = '\n'.join(output) | 
					
						
							|  |  |  |         if filename: | 
					
						
							|  |  |  |             lineno = 8 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             lineno = 10 | 
					
						
							|  |  |  |         regex = """
 | 
					
						
							|  |  |  | ^Thread 0x[0-9a-f]+: | 
					
						
							| 
									
										
										
										
											2011-04-03 18:41:22 +02:00
										 |  |  | (?:  File ".*threading.py", line [0-9]+ in [_a-z]+ | 
					
						
							|  |  |  | ){{1,3}}  File "<string>", line 23 in run | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |   File ".*threading.py", line [0-9]+ in _bootstrap_inner | 
					
						
							|  |  |  |   File ".*threading.py", line [0-9]+ in _bootstrap | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Current thread XXX: | 
					
						
							|  |  |  |   File "<string>", line {lineno} in dump | 
					
						
							|  |  |  |   File "<string>", line 28 in <module>$ | 
					
						
							|  |  |  | """.strip()
 | 
					
						
							|  |  |  |         regex = regex.format(lineno=lineno) | 
					
						
							|  |  |  |         self.assertRegex(output, regex) | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |         self.assertEqual(exitcode, 0) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_dump_traceback_threads(self): | 
					
						
							|  |  |  |         self.check_dump_traceback_threads(None) | 
					
						
							| 
									
										
										
										
											2011-03-31 18:15:52 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_dump_traceback_threads_file(self): | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         with temporary_filename() as filename: | 
					
						
							|  |  |  |             self.check_dump_traceback_threads(filename) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-04-08 12:57:06 +02:00
										 |  |  |     def _check_dump_tracebacks_later(self, repeat, cancel, filename, loops): | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Check how many times the traceback is written in timeout x 2.5 seconds, | 
					
						
							|  |  |  |         or timeout x 3.5 seconds if cancel is True: 1, 2 or 3 times depending | 
					
						
							|  |  |  |         on repeat and cancel options. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Raise an error if the output doesn't match the expect format. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2011-04-08 13:39:59 +02:00
										 |  |  |         timeout_str = str(datetime.timedelta(seconds=TIMEOUT)) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         code = """
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | import time | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-04-08 12:57:06 +02:00
										 |  |  | def func(timeout, repeat, cancel, file, loops): | 
					
						
							|  |  |  |     for loop in range(loops): | 
					
						
							|  |  |  |         faulthandler.dump_tracebacks_later(timeout, repeat=repeat, file=file) | 
					
						
							|  |  |  |         if cancel: | 
					
						
							|  |  |  |             faulthandler.cancel_dump_tracebacks_later() | 
					
						
							| 
									
										
										
										
											2011-06-01 13:49:12 +02:00
										 |  |  |         time.sleep(timeout * 5) | 
					
						
							| 
									
										
										
										
											2011-04-03 18:45:42 +02:00
										 |  |  |         faulthandler.cancel_dump_tracebacks_later() | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-04-01 15:37:12 +02:00
										 |  |  | timeout = {timeout} | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | repeat = {repeat} | 
					
						
							|  |  |  | cancel = {cancel} | 
					
						
							| 
									
										
										
										
											2011-04-08 12:57:06 +02:00
										 |  |  | loops = {loops} | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | if {has_filename}: | 
					
						
							|  |  |  |     file = open({filename}, "wb") | 
					
						
							|  |  |  | else: | 
					
						
							|  |  |  |     file = None | 
					
						
							| 
									
										
										
										
											2011-04-08 12:57:06 +02:00
										 |  |  | func(timeout, repeat, cancel, file, loops) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | if file is not None: | 
					
						
							|  |  |  |     file.close() | 
					
						
							|  |  |  | """.strip()
 | 
					
						
							|  |  |  |         code = code.format( | 
					
						
							| 
									
										
										
										
											2011-04-08 12:57:06 +02:00
										 |  |  |             timeout=TIMEOUT, | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |             repeat=repeat, | 
					
						
							|  |  |  |             cancel=cancel, | 
					
						
							| 
									
										
										
										
											2011-04-08 12:57:06 +02:00
										 |  |  |             loops=loops, | 
					
						
							|  |  |  |             has_filename=bool(filename), | 
					
						
							|  |  |  |             filename=repr(filename), | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |         trace, exitcode = self.get_output(code, filename) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         trace = '\n'.join(trace) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-04-03 18:45:42 +02:00
										 |  |  |         if not cancel: | 
					
						
							| 
									
										
										
										
											2011-04-08 12:57:06 +02:00
										 |  |  |             count = loops | 
					
						
							| 
									
										
										
										
											2011-04-03 18:45:42 +02:00
										 |  |  |             if repeat: | 
					
						
							| 
									
										
										
										
											2011-04-08 12:57:06 +02:00
										 |  |  |                 count *= 2 | 
					
						
							| 
									
										
										
										
											2011-04-08 13:39:59 +02:00
										 |  |  |             header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+:\n' % timeout_str | 
					
						
							| 
									
										
										
										
											2011-06-01 13:49:12 +02:00
										 |  |  |             regex = expected_traceback(9, 20, header, min_count=count) | 
					
						
							| 
									
										
										
										
											2011-04-03 18:45:42 +02:00
										 |  |  |             self.assertRegex(trace, regex) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2011-04-03 18:45:42 +02:00
										 |  |  |             self.assertEqual(trace, '') | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |         self.assertEqual(exitcode, 0) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @unittest.skipIf(not hasattr(faulthandler, 'dump_tracebacks_later'), | 
					
						
							|  |  |  |                      'need faulthandler.dump_tracebacks_later()') | 
					
						
							|  |  |  |     def check_dump_tracebacks_later(self, repeat=False, cancel=False, | 
					
						
							| 
									
										
										
										
											2011-04-08 12:57:06 +02:00
										 |  |  |                                     file=False, twice=False): | 
					
						
							|  |  |  |         if twice: | 
					
						
							|  |  |  |             loops = 2 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             loops = 1 | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         if file: | 
					
						
							|  |  |  |             with temporary_filename() as filename: | 
					
						
							| 
									
										
										
										
											2011-04-08 12:57:06 +02:00
										 |  |  |                 self._check_dump_tracebacks_later(repeat, cancel, | 
					
						
							|  |  |  |                                                   filename, loops) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2011-04-08 12:57:06 +02:00
										 |  |  |             self._check_dump_tracebacks_later(repeat, cancel, None, loops) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_dump_tracebacks_later(self): | 
					
						
							|  |  |  |         self.check_dump_tracebacks_later() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_dump_tracebacks_later_repeat(self): | 
					
						
							|  |  |  |         self.check_dump_tracebacks_later(repeat=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-04-03 18:45:42 +02:00
										 |  |  |     def test_dump_tracebacks_later_cancel(self): | 
					
						
							|  |  |  |         self.check_dump_tracebacks_later(cancel=True) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_dump_tracebacks_later_file(self): | 
					
						
							|  |  |  |         self.check_dump_tracebacks_later(file=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-04-08 12:57:06 +02:00
										 |  |  |     def test_dump_tracebacks_later_twice(self): | 
					
						
							|  |  |  |         self.check_dump_tracebacks_later(twice=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |     @unittest.skipIf(not hasattr(faulthandler, "register"), | 
					
						
							|  |  |  |                      "need faulthandler.register") | 
					
						
							| 
									
										
										
										
											2011-04-01 12:56:17 +02:00
										 |  |  |     def check_register(self, filename=False, all_threads=False, | 
					
						
							| 
									
										
										
										
											2011-07-13 23:39:53 +02:00
										 |  |  |                        unregister=False, chain=False): | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Register a handler displaying the traceback on a user signal. Raise the | 
					
						
							|  |  |  |         signal and check the written traceback. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-07-13 23:39:53 +02:00
										 |  |  |         If chain is True, check that the previous signal handler is called. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         Raise an error if the output doesn't match the expected format. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2011-04-01 12:56:17 +02:00
										 |  |  |         signum = signal.SIGUSR1 | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         code = """
 | 
					
						
							|  |  |  | import faulthandler | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import signal | 
					
						
							| 
									
										
										
										
											2011-07-13 23:39:53 +02:00
										 |  |  | import sys | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | def func(signum): | 
					
						
							|  |  |  |     os.kill(os.getpid(), signum) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-07-13 23:39:53 +02:00
										 |  |  | def handler(signum, frame): | 
					
						
							|  |  |  |     handler.called = True | 
					
						
							|  |  |  | handler.called = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | exitcode = 0 | 
					
						
							| 
									
										
										
										
											2011-04-01 12:56:17 +02:00
										 |  |  | signum = {signum} | 
					
						
							|  |  |  | unregister = {unregister} | 
					
						
							| 
									
										
										
										
											2011-07-13 23:39:53 +02:00
										 |  |  | chain = {chain} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | if {has_filename}: | 
					
						
							|  |  |  |     file = open({filename}, "wb") | 
					
						
							|  |  |  | else: | 
					
						
							|  |  |  |     file = None | 
					
						
							| 
									
										
										
										
											2011-07-13 23:39:53 +02:00
										 |  |  | if chain: | 
					
						
							|  |  |  |     signal.signal(signum, handler) | 
					
						
							|  |  |  | faulthandler.register(signum, file=file, | 
					
						
							|  |  |  |                       all_threads={all_threads}, chain={chain}) | 
					
						
							| 
									
										
										
										
											2011-04-01 12:56:17 +02:00
										 |  |  | if unregister: | 
					
						
							|  |  |  |     faulthandler.unregister(signum) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | func(signum) | 
					
						
							| 
									
										
										
										
											2011-07-13 23:39:53 +02:00
										 |  |  | if chain and not handler.called: | 
					
						
							|  |  |  |     if file is not None: | 
					
						
							|  |  |  |         output = file | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         output = sys.stderr | 
					
						
							|  |  |  |     print("Error: signal handler not called!", file=output) | 
					
						
							|  |  |  |     exitcode = 1 | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | if file is not None: | 
					
						
							|  |  |  |     file.close() | 
					
						
							| 
									
										
										
										
											2011-07-13 23:39:53 +02:00
										 |  |  | sys.exit(exitcode) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | """.strip()
 | 
					
						
							|  |  |  |         code = code.format( | 
					
						
							|  |  |  |             filename=repr(filename), | 
					
						
							|  |  |  |             has_filename=bool(filename), | 
					
						
							|  |  |  |             all_threads=all_threads, | 
					
						
							| 
									
										
										
										
											2011-04-01 12:56:17 +02:00
										 |  |  |             signum=signum, | 
					
						
							|  |  |  |             unregister=unregister, | 
					
						
							| 
									
										
										
										
											2011-07-13 23:39:53 +02:00
										 |  |  |             chain=chain, | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2011-03-31 13:29:56 +02:00
										 |  |  |         trace, exitcode = self.get_output(code, filename) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         trace = '\n'.join(trace) | 
					
						
							| 
									
										
										
										
											2011-04-01 12:56:17 +02:00
										 |  |  |         if not unregister: | 
					
						
							|  |  |  |             if all_threads: | 
					
						
							|  |  |  |                 regex = 'Current thread XXX:\n' | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 regex = 'Traceback \(most recent call first\):\n' | 
					
						
							| 
									
										
										
										
											2011-07-13 23:39:53 +02:00
										 |  |  |             regex = expected_traceback(7, 28, regex) | 
					
						
							| 
									
										
										
										
											2011-04-01 12:56:17 +02:00
										 |  |  |             self.assertRegex(trace, regex) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2011-04-01 12:56:17 +02:00
										 |  |  |             self.assertEqual(trace, '') | 
					
						
							|  |  |  |         if unregister: | 
					
						
							|  |  |  |             self.assertNotEqual(exitcode, 0) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.assertEqual(exitcode, 0) | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def test_register(self): | 
					
						
							|  |  |  |         self.check_register() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-04-01 12:56:17 +02:00
										 |  |  |     def test_unregister(self): | 
					
						
							|  |  |  |         self.check_register(unregister=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  |     def test_register_file(self): | 
					
						
							|  |  |  |         with temporary_filename() as filename: | 
					
						
							|  |  |  |             self.check_register(filename=filename) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_register_threads(self): | 
					
						
							|  |  |  |         self.check_register(all_threads=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-07-13 23:39:53 +02:00
										 |  |  |     def test_register_chain(self): | 
					
						
							|  |  |  |         self.check_register(chain=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-31 01:31:06 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | def test_main(): | 
					
						
							|  |  |  |     support.run_unittest(FaultHandlerTests) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     test_main() |