-
Notifications
You must be signed in to change notification settings - Fork 12
Clearer failure/errors in failure_reason & failure_expanded
#64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| """Buildkite Test Engine PyTest failure reason mapping""" | ||
|
|
||
| from __future__ import annotations | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just for Python 3.9 which we'll drop pretty soon, it's EOL in a couple of months. |
||
| from typing import Iterable, Mapping | ||
|
|
||
| # importing these privates isn't ideal, but we're only using them for type checking | ||
| from _pytest._code.code import ExceptionInfo, ExceptionRepr, TerminalRepr | ||
|
|
||
| # pylint: disable=too-many-locals | ||
| # pylint: disable=too-many-return-statements | ||
| def failure_reasons( | ||
| longrepr: None | ExceptionInfo[BaseException] | tuple[str, int, str] | str | TerminalRepr | ||
| ) -> tuple[str | None, Iterable[Mapping[str, Iterable[str]]] | None]: | ||
| """ | ||
| Derives Buildkite's failure_reason & failure_expanded from PyTest's longrepr. | ||
|
|
||
| Args: | ||
| longrepr: The PyTest longrepr object containing failure information | ||
|
|
||
| Returns: | ||
| A tuple containing: | ||
| - A string with the failure reason or None if not available | ||
| - A list of mappings with additional failure details or None if not available | ||
| """ | ||
| if longrepr is None: | ||
| return None, None | ||
|
|
||
| if isinstance(longrepr, str): | ||
| return _handle_string_longrepr(longrepr) | ||
|
|
||
| if (isinstance(longrepr, tuple) and len(longrepr) == 3 and | ||
| isinstance(longrepr[0], str) and | ||
| isinstance(longrepr[1], int) and | ||
| isinstance(longrepr[2], str)): | ||
| path, line, msg = longrepr | ||
| return _handle_tuple_longrepr(path, line, msg) | ||
|
|
||
| if isinstance(longrepr, ExceptionInfo): | ||
| return _handle_exception_info_longrepr(longrepr) | ||
|
|
||
| if isinstance(longrepr, ExceptionRepr) and longrepr.reprcrash is not None: | ||
| return _handle_exception_repr_longrepr(longrepr) | ||
|
|
||
| return _handle_default_longrepr(longrepr) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm trying to understand Pytest's Do we need to handle when it's a
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That can be handled by the default |
||
|
|
||
|
|
||
| def _handle_string_longrepr( | ||
| s: str | ||
| ) -> tuple[str | None, Iterable[Mapping[str, Iterable[str]]] | None]: | ||
| """Handle string longrepr case""" | ||
| lines = s.splitlines() | ||
| if len(lines) == 0: | ||
| return None, None | ||
| if len(lines) == 1: | ||
| return lines[0], None | ||
| return lines[0], [{"expanded": lines[1:]}] | ||
|
|
||
|
|
||
| def _handle_tuple_longrepr( | ||
| path: str, | ||
| line: int, | ||
| msg: str | ||
| ) -> tuple[str | None, Iterable[Mapping[str, Iterable[str]]] | None]: | ||
| """Handle tuple longrepr case (path, line, msg)""" | ||
| failure_reason = msg | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any chance the tuple could have a very long message?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possibly. We truncate it server side in that case. I think this code path might only happen when the |
||
| return failure_reason, [{"expanded": [], "backtrace": [f"{path}:{line}"]}] | ||
|
|
||
|
|
||
| def _handle_exception_info_longrepr( | ||
| exc_info: ExceptionInfo[BaseException] | ||
| ) -> tuple[str | None, Iterable[Mapping[str, Iterable[str]]] | None]: | ||
| """Handle ExceptionInfo longrepr case""" | ||
| failure_reason = exc_info.exconly() | ||
| expanded = [] | ||
| backtrace = [] | ||
|
|
||
| if hasattr(exc_info, "traceback") and exc_info.traceback: | ||
| for entry in exc_info.traceback: | ||
| backtrace.append(f"{entry.path}:{entry.lineno}: {entry.name}") | ||
| source = entry.getsource() if hasattr(entry, "getsource") else None | ||
| if source: | ||
| expanded.extend(str(line) for line in source) | ||
|
|
||
| failure_expanded = {} | ||
| if len(expanded) > 0: | ||
| failure_expanded["expanded"] = expanded | ||
| if len(backtrace) > 0: | ||
| failure_expanded["backtrace"] = backtrace | ||
|
|
||
| return failure_reason, [failure_expanded] | ||
|
|
||
|
|
||
| def _handle_exception_repr_longrepr( | ||
| er: ExceptionRepr | ||
| ) -> tuple[str | None, Iterable[Mapping[str, Iterable[str]]] | None]: | ||
| """Handle ExceptionRepr longrepr case""" | ||
| failure_reason = er.reprcrash.message # e.g. "ZeroDivisionError: division by zero" | ||
| failure_expanded = [{"expanded": str(er).splitlines()}] | ||
| try: | ||
| failure_expanded[0]["backtrace"] = [ | ||
| str(getattr(entry, 'reprfileloc', entry)) | ||
| for entry in er.reprtraceback.reprentries | ||
| ] | ||
| except AttributeError: | ||
| pass | ||
| return failure_reason, failure_expanded | ||
|
|
||
|
|
||
| def _handle_default_longrepr( | ||
| longrepr: None | ExceptionInfo[BaseException] | tuple[str, int, str] | str | TerminalRepr | ||
| ) -> tuple[str | None, Iterable[Mapping[str, Iterable[str]]] | None]: | ||
| """Handle default longrepr case""" | ||
| return _handle_string_longrepr(str(longrepr)) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a huge fan of this data structure but understand it's because of the existing API :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
➕