Make an attempt at having taskflow exceptions print causes better
It is often quite useful to try to see what the contained causes are on versions of python that do not have the native support for this built-in so to make everyones life easier add basic support for traversing the cause list and printing out associated causes when we are able to. Change-Id: Ia0a7e13757a989722291bcc06599d04014706d8c
This commit is contained in:
@@ -18,6 +18,7 @@ import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from oslo_utils import reflection
|
||||
import six
|
||||
|
||||
|
||||
@@ -76,35 +77,51 @@ class TaskFlowException(Exception):
|
||||
def cause(self):
|
||||
return self._cause
|
||||
|
||||
def pformat(self, indent=2, indent_text=" "):
|
||||
def __str__(self):
|
||||
return self.pformat()
|
||||
|
||||
def _get_message(self):
|
||||
# We must *not* call into the __str__ method as that will reactivate
|
||||
# the pformat method, which will end up badly (and doesn't look
|
||||
# pretty at all); so be careful...
|
||||
return self.args[0]
|
||||
|
||||
def pformat(self, indent=2, indent_text=" ", show_root_class=False):
|
||||
"""Pretty formats a taskflow exception + any connected causes."""
|
||||
if indent < 0:
|
||||
raise ValueError("indent must be greater than or equal to zero")
|
||||
return os.linesep.join(self._pformat(self, [], 0,
|
||||
indent=indent,
|
||||
indent_text=indent_text))
|
||||
|
||||
@classmethod
|
||||
def _pformat(cls, excp, lines, current_indent, indent=2, indent_text=" "):
|
||||
line_prefix = indent_text * current_indent
|
||||
for line in traceback.format_exception_only(type(excp), excp):
|
||||
# We'll add our own newlines on at the end of formatting.
|
||||
#
|
||||
# NOTE(harlowja): the reason we don't search for os.linesep is
|
||||
# that the traceback module seems to only use '\n' (for some
|
||||
# reason).
|
||||
if line.endswith("\n"):
|
||||
line = line[0:-1]
|
||||
lines.append(line_prefix + line)
|
||||
try:
|
||||
cause = excp.cause
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if cause is not None:
|
||||
cls._pformat(cause, lines, current_indent + indent,
|
||||
indent=indent, indent_text=indent_text)
|
||||
return lines
|
||||
raise ValueError("Provided 'indent' must be greater than"
|
||||
" or equal to zero instead of %s" % indent)
|
||||
buf = six.StringIO()
|
||||
if show_root_class:
|
||||
buf.write(reflection.get_class_name(self, fully_qualified=False))
|
||||
buf.write(": ")
|
||||
buf.write(self._get_message())
|
||||
active_indent = indent
|
||||
next_up = self.cause
|
||||
while next_up is not None:
|
||||
buf.write(os.linesep)
|
||||
if isinstance(next_up, TaskFlowException):
|
||||
buf.write(indent_text * active_indent)
|
||||
buf.write(reflection.get_class_name(next_up,
|
||||
fully_qualified=False))
|
||||
buf.write(": ")
|
||||
buf.write(next_up._get_message())
|
||||
else:
|
||||
lines = traceback.format_exception_only(type(next_up), next_up)
|
||||
for i, line in enumerate(lines):
|
||||
buf.write(indent_text * active_indent)
|
||||
if line.endswith("\n"):
|
||||
# We'll add our own newlines on...
|
||||
line = line[0:-1]
|
||||
buf.write(line)
|
||||
if i + 1 != len(lines):
|
||||
buf.write(os.linesep)
|
||||
active_indent += indent
|
||||
try:
|
||||
next_up = next_up.cause
|
||||
except AttributeError:
|
||||
next_up = None
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
# Errors related to storage or operations on storage units.
|
||||
|
||||
@@ -56,6 +56,44 @@ class TestExceptions(test.TestCase):
|
||||
self.assertIsNotNone(capture.cause)
|
||||
self.assertIsInstance(capture.cause, IOError)
|
||||
|
||||
def test_pformat_str(self):
|
||||
ex = None
|
||||
try:
|
||||
try:
|
||||
try:
|
||||
raise IOError("Didn't work")
|
||||
except IOError:
|
||||
exc.raise_with_cause(exc.TaskFlowException,
|
||||
"It didn't go so well")
|
||||
except exc.TaskFlowException:
|
||||
exc.raise_with_cause(exc.TaskFlowException, "I Failed")
|
||||
except exc.TaskFlowException as e:
|
||||
ex = e
|
||||
|
||||
self.assertIsNotNone(ex)
|
||||
self.assertIsInstance(ex, exc.TaskFlowException)
|
||||
self.assertIsInstance(ex.cause, exc.TaskFlowException)
|
||||
self.assertIsInstance(ex.cause.cause, IOError)
|
||||
|
||||
p_msg = ex.pformat()
|
||||
p_str_msg = str(ex)
|
||||
for msg in ["I Failed", "It didn't go so well", "Didn't work"]:
|
||||
self.assertIn(msg, p_msg)
|
||||
self.assertIn(msg, p_str_msg)
|
||||
|
||||
def test_pformat_root_class(self):
|
||||
ex = exc.TaskFlowException("Broken")
|
||||
self.assertIn("TaskFlowException",
|
||||
ex.pformat(show_root_class=True))
|
||||
self.assertNotIn("TaskFlowException",
|
||||
ex.pformat(show_root_class=False))
|
||||
self.assertIn("Broken",
|
||||
ex.pformat(show_root_class=True))
|
||||
|
||||
def test_invalid_pformat_indent(self):
|
||||
ex = exc.TaskFlowException("Broken")
|
||||
self.assertRaises(ValueError, ex.pformat, indent=-100)
|
||||
|
||||
@testtools.skipIf(not six.PY3, 'py3.x is not available')
|
||||
def test_raise_with_cause(self):
|
||||
capture = None
|
||||
|
||||
Reference in New Issue
Block a user