summaryrefslogtreecommitdiff
path: root/Lib/test/test_faulthandler.py
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2015-03-12 15:32:03 +0100
committerVictor Stinner <victor.stinner@gmail.com>2015-03-12 15:32:03 +0100
commitda1eb8aba9f5fa159b7e2cc1794da81bae56ee9a (patch)
tree19a9320337f436b4a53f4c37a24e450cd7cabb3b /Lib/test/test_faulthandler.py
parentb4d726f3b379fd28b021237669231da7041d8d72 (diff)
downloadcpython-da1eb8aba9f5fa159b7e2cc1794da81bae56ee9a.tar.gz
Issue #23566: enable(), register(), dump_traceback() and dump_traceback_later()
functions of faulthandler now accept file descriptors. Patch by Wei Wu.
Diffstat (limited to 'Lib/test/test_faulthandler.py')
-rw-r--r--Lib/test/test_faulthandler.py158
1 files changed, 101 insertions, 57 deletions
diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
index 35129b6a71..bbefae7902 100644
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -42,7 +42,7 @@ def temporary_filename():
support.unlink(filename)
class FaultHandlerTests(unittest.TestCase):
- def get_output(self, code, filename=None):
+ def get_output(self, code, filename=None, fd=None):
"""
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).
@@ -53,8 +53,11 @@ class FaultHandlerTests(unittest.TestCase):
thread XXX".
"""
code = dedent(code).strip()
+ pass_fds = []
+ if fd is not None:
+ pass_fds.append(fd)
with support.SuppressCrashReport():
- process = script_helper.spawn_python('-c', code)
+ process = script_helper.spawn_python('-c', code, pass_fds=pass_fds)
stdout, stderr = process.communicate()
exitcode = process.wait()
output = support.strip_python_stderr(stdout)
@@ -64,13 +67,20 @@ class FaultHandlerTests(unittest.TestCase):
with open(filename, "rb") as fp:
output = fp.read()
output = output.decode('ascii', 'backslashreplace')
+ elif fd is not None:
+ self.assertEqual(output, '')
+ os.lseek(fd, os.SEEK_SET, 0)
+ with open(fd, "rb", closefd=False) as fp:
+ output = fp.read()
+ output = output.decode('ascii', 'backslashreplace')
output = re.sub('Current thread 0x[0-9a-f]+',
'Current thread XXX',
output)
return output.splitlines(), exitcode
def check_fatal_error(self, code, line_number, name_regex,
- filename=None, all_threads=True, other_regex=None):
+ filename=None, all_threads=True, other_regex=None,
+ fd=None):
"""
Check that the fault handler for fatal errors is enabled and check the
traceback from the child process output.
@@ -93,7 +103,7 @@ class FaultHandlerTests(unittest.TestCase):
header=re.escape(header))).strip()
if other_regex:
regex += '|' + other_regex
- output, exitcode = self.get_output(code, filename)
+ output, exitcode = self.get_output(code, filename=filename, fd=fd)
output = '\n'.join(output)
self.assertRegex(output, regex)
self.assertNotEqual(exitcode, 0)
@@ -211,6 +221,19 @@ class FaultHandlerTests(unittest.TestCase):
'Segmentation fault',
filename=filename)
+ def test_enable_fd(self):
+ with tempfile.TemporaryFile('wb+') as fp:
+ fd = fp.fileno()
+ self.check_fatal_error("""
+ import faulthandler
+ import sys
+ faulthandler.enable(%s)
+ faulthandler._sigsegv()
+ """ % fd,
+ 4,
+ 'Segmentation fault',
+ fd=fd)
+
def test_enable_single_thread(self):
self.check_fatal_error("""
import faulthandler
@@ -297,7 +320,7 @@ class FaultHandlerTests(unittest.TestCase):
output = subprocess.check_output(args, env=env)
self.assertEqual(output.rstrip(), b"True")
- def check_dump_traceback(self, filename):
+ def check_dump_traceback(self, *, filename=None, fd=None):
"""
Explicitly call dump_traceback() function and check its output.
Raise an error if the output doesn't match the expected format.
@@ -305,10 +328,16 @@ class FaultHandlerTests(unittest.TestCase):
code = """
import faulthandler
+ filename = {filename!r}
+ fd = {fd}
+
def funcB():
- if {has_filename}:
- with open({filename}, "wb") as fp:
+ if filename:
+ with open(filename, "wb") as fp:
faulthandler.dump_traceback(fp, all_threads=False)
+ elif fd is not None:
+ faulthandler.dump_traceback(fd,
+ all_threads=False)
else:
faulthandler.dump_traceback(all_threads=False)
@@ -318,29 +347,35 @@ class FaultHandlerTests(unittest.TestCase):
funcA()
"""
code = code.format(
- filename=repr(filename),
- has_filename=bool(filename),
+ filename=filename,
+ fd=fd,
)
if filename:
- lineno = 6
+ lineno = 9
+ elif fd is not None:
+ lineno = 12
else:
- lineno = 8
+ lineno = 14
expected = [
'Stack (most recent call first):',
' File "<string>", line %s in funcB' % lineno,
- ' File "<string>", line 11 in funcA',
- ' File "<string>", line 13 in <module>'
+ ' File "<string>", line 17 in funcA',
+ ' File "<string>", line 19 in <module>'
]
- trace, exitcode = self.get_output(code, filename)
+ trace, exitcode = self.get_output(code, filename, fd)
self.assertEqual(trace, expected)
self.assertEqual(exitcode, 0)
def test_dump_traceback(self):
- self.check_dump_traceback(None)
+ self.check_dump_traceback()
def test_dump_traceback_file(self):
with temporary_filename() as filename:
- self.check_dump_traceback(filename)
+ self.check_dump_traceback(filename=filename)
+
+ def test_dump_traceback_fd(self):
+ with tempfile.TemporaryFile('wb+') as fp:
+ self.check_dump_traceback(fd=fp.fileno())
def test_truncate(self):
maxlen = 500
@@ -433,7 +468,10 @@ class FaultHandlerTests(unittest.TestCase):
with temporary_filename() as filename:
self.check_dump_traceback_threads(filename)
- def _check_dump_traceback_later(self, repeat, cancel, filename, loops):
+ @unittest.skipIf(not hasattr(faulthandler, 'dump_traceback_later'),
+ 'need faulthandler.dump_traceback_later()')
+ def check_dump_traceback_later(self, repeat=False, cancel=False, loops=1,
+ *, filename=None, fd=None):
"""
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
@@ -445,6 +483,14 @@ class FaultHandlerTests(unittest.TestCase):
code = """
import faulthandler
import time
+ import sys
+
+ timeout = {timeout}
+ repeat = {repeat}
+ cancel = {cancel}
+ loops = {loops}
+ filename = {filename!r}
+ fd = {fd}
def func(timeout, repeat, cancel, file, loops):
for loop in range(loops):
@@ -454,16 +500,14 @@ class FaultHandlerTests(unittest.TestCase):
time.sleep(timeout * 5)
faulthandler.cancel_dump_traceback_later()
- timeout = {timeout}
- repeat = {repeat}
- cancel = {cancel}
- loops = {loops}
- if {has_filename}:
- file = open({filename}, "wb")
+ if filename:
+ file = open(filename, "wb")
+ elif fd is not None:
+ file = sys.stderr.fileno()
else:
file = None
func(timeout, repeat, cancel, file, loops)
- if file is not None:
+ if filename:
file.close()
"""
code = code.format(
@@ -471,8 +515,8 @@ class FaultHandlerTests(unittest.TestCase):
repeat=repeat,
cancel=cancel,
loops=loops,
- has_filename=bool(filename),
- filename=repr(filename),
+ filename=filename,
+ fd=fd,
)
trace, exitcode = self.get_output(code, filename)
trace = '\n'.join(trace)
@@ -482,27 +526,12 @@ class FaultHandlerTests(unittest.TestCase):
if repeat:
count *= 2
header = r'Timeout \(%s\)!\nThread 0x[0-9a-f]+ \(most recent call first\):\n' % timeout_str
- regex = expected_traceback(9, 20, header, min_count=count)
+ regex = expected_traceback(17, 26, header, min_count=count)
self.assertRegex(trace, regex)
else:
self.assertEqual(trace, '')
self.assertEqual(exitcode, 0)
- @unittest.skipIf(not hasattr(faulthandler, 'dump_traceback_later'),
- 'need faulthandler.dump_traceback_later()')
- def check_dump_traceback_later(self, repeat=False, cancel=False,
- file=False, twice=False):
- if twice:
- loops = 2
- else:
- loops = 1
- if file:
- with temporary_filename() as filename:
- self._check_dump_traceback_later(repeat, cancel,
- filename, loops)
- else:
- self._check_dump_traceback_later(repeat, cancel, None, loops)
-
def test_dump_traceback_later(self):
self.check_dump_traceback_later()
@@ -513,15 +542,20 @@ class FaultHandlerTests(unittest.TestCase):
self.check_dump_traceback_later(cancel=True)
def test_dump_traceback_later_file(self):
- self.check_dump_traceback_later(file=True)
+ with temporary_filename() as filename:
+ self.check_dump_traceback_later(filename=filename)
+
+ def test_dump_traceback_later_fd(self):
+ with tempfile.TemporaryFile('wb+') as fp:
+ self.check_dump_traceback_later(fd=fp.fileno())
def test_dump_traceback_later_twice(self):
- self.check_dump_traceback_later(twice=True)
+ self.check_dump_traceback_later(loops=2)
@unittest.skipIf(not hasattr(faulthandler, "register"),
"need faulthandler.register")
def check_register(self, filename=False, all_threads=False,
- unregister=False, chain=False):
+ unregister=False, chain=False, fd=None):
"""
Register a handler displaying the traceback on a user signal. Raise the
signal and check the written traceback.
@@ -537,6 +571,13 @@ class FaultHandlerTests(unittest.TestCase):
import signal
import sys
+ all_threads = {all_threads}
+ signum = {signum}
+ unregister = {unregister}
+ chain = {chain}
+ filename = {filename!r}
+ fd = {fd}
+
def func(signum):
os.kill(os.getpid(), signum)
@@ -544,19 +585,16 @@ class FaultHandlerTests(unittest.TestCase):
handler.called = True
handler.called = False
- exitcode = 0
- signum = {signum}
- unregister = {unregister}
- chain = {chain}
-
- if {has_filename}:
- file = open({filename}, "wb")
+ if filename:
+ file = open(filename, "wb")
+ elif fd is not None:
+ file = sys.stderr.fileno()
else:
file = None
if chain:
signal.signal(signum, handler)
faulthandler.register(signum, file=file,
- all_threads={all_threads}, chain={chain})
+ all_threads=all_threads, chain={chain})
if unregister:
faulthandler.unregister(signum)
func(signum)
@@ -567,17 +605,19 @@ class FaultHandlerTests(unittest.TestCase):
output = sys.stderr
print("Error: signal handler not called!", file=output)
exitcode = 1
- if file is not None:
+ else:
+ exitcode = 0
+ if filename:
file.close()
sys.exit(exitcode)
"""
code = code.format(
- filename=repr(filename),
- has_filename=bool(filename),
all_threads=all_threads,
signum=signum,
unregister=unregister,
chain=chain,
+ filename=filename,
+ fd=fd,
)
trace, exitcode = self.get_output(code, filename)
trace = '\n'.join(trace)
@@ -586,7 +626,7 @@ class FaultHandlerTests(unittest.TestCase):
regex = 'Current thread XXX \(most recent call first\):\n'
else:
regex = 'Stack \(most recent call first\):\n'
- regex = expected_traceback(7, 28, regex)
+ regex = expected_traceback(14, 32, regex)
self.assertRegex(trace, regex)
else:
self.assertEqual(trace, '')
@@ -605,6 +645,10 @@ class FaultHandlerTests(unittest.TestCase):
with temporary_filename() as filename:
self.check_register(filename=filename)
+ def test_register_fd(self):
+ with tempfile.TemporaryFile('wb+') as fp:
+ self.check_register(fd=fp.fileno())
+
def test_register_threads(self):
self.check_register(all_threads=True)