class ErrorDetails:
"""Object wrapping the last occurred exception.
It has attributes `message`, `traceback`, and `error`, where `message` contains
the message with possible generic exception name removed, `traceback` contains
the traceback and `error` contains the original error instance.
"""
_generic_names = frozenset(('AssertionError', 'Error', 'Exception', 'RuntimeError'))
def __init__(self, error=None, full_traceback=True,
exclude_robot_traces=EXCLUDE_ROBOT_TRACES):
if not error:
error = sys.exc_info()[1]
if isinstance(error, RERAISED_EXCEPTIONS):
raise error
self.error = error
self._full_traceback = full_traceback
self._exclude_robot_traces = exclude_robot_traces
self._message = None
self._traceback = None
@property
def message(self):
if self._message is None:
self._message = self._format_message(self.error)
return self._message
@property
def traceback(self):
if self._traceback is None:
self._traceback = self._format_traceback(self.error)
return self._traceback
def _format_traceback(self, error):
if isinstance(error, RobotError):
return error.details
if self._exclude_robot_traces:
self._remove_robot_traces(error)
lines = self._get_traceback_lines(type(error), error, error.__traceback__)
return ''.join(lines).rstrip()
def _remove_robot_traces(self, error):
tb = error.__traceback__
while tb and self._is_robot_traceback(tb):
tb = tb.tb_next
error.__traceback__ = tb
if error.__context__:
self._remove_robot_traces(error.__context__)
if error.__cause__:
self._remove_robot_traces(error.__cause__)
def _is_robot_traceback(self, tb):
module = tb.tb_frame.f_globals.get('__name__')
return module and module.startswith('robot.')
def _get_traceback_lines(self, etype, value, tb):
prefix = 'Traceback (most recent call last):\n'
empty_tb = [prefix, ' None\n']
if self._full_traceback:
if tb or value.__context__ or value.__cause__:
return traceback.format_exception(etype, value, tb)
else:
return empty_tb + traceback.format_exception_only(etype, value)
else:
if tb:
return [prefix] + traceback.format_tb(tb)
else:
return empty_tb
def _format_message(self, error):
name = type(error).__name__.split('.')[-1] # Use only the last part
message = str(error)
if not message:
return name
if self._suppress_name(name, error):
return message
if message.startswith('*HTML*'):
name = '*HTML* ' + name
message = message.split('*', 2)[-1].lstrip()
return '%s: %s' % (name, message)
def _suppress_name(self, name, error):
return (name in self._generic_names
or isinstance(error, RobotError)
or getattr(error, 'ROBOT_SUPPRESS_NAME', False))