Skip to content

Clearer failure/errors in failure_reason & failure_expanded#64

Merged
pda merged 4 commits intomainfrom
failure-expanded
Jul 21, 2025
Merged

Clearer failure/errors in failure_reason & failure_expanded#64
pda merged 4 commits intomainfrom
failure-expanded

Conversation

@pda
Copy link
Member

@pda pda commented Jul 18, 2025

Currently, the entire failure from PyTest (PyTest calls it longreprtext) is set as failure_reason.

However, failure_reason is intended for a short one-line summary, and gets truncated to ~1KIB, which isn't great for longreprtext.

Buildkite also supports failure_expanded, for including detailed information and backtraces on errors.

So, derive that from the various shapes that longrepr can take, and include them in the uploaded JSON payload.

Before After
Unclear failure_reason summaries:
image
Useful failure/error messages:
image
Truncated unclear expanded text which doesn't even mention the ZeroDivisionError:
image
Full expanded information and stack trace:
image

Currently, the entire failure from PyTest (PyTest calls it longreprtext) is
set as failure_reason.

However, failure_reason is intended for a short one-line summary, and
gets truncated to ~1KIB, which isn't great for longreprtext.

Buildkite also supports failure_expanded, to included detailed
information and backtraces on errors.

So, derive that from the various shapes that longrepr can take, and
include them in the uploaded JSON payload.
@pda pda requested review from a team and Copilot July 18, 2025 06:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR improves the handling of test failure information in the Buildkite Test Analytics collector by providing clearer failure summaries and detailed expanded information. The changes separate the short failure reason (for UI display) from the detailed failure information (for expanded views).

Key changes:

  • Added a new failure_expanded field to TestResultFailed to store detailed failure information including stack traces
  • Implemented failure_reasons() function to parse different PyTest longrepr formats and extract appropriate failure summaries
  • Updated the plugin to use the new failure parsing logic instead of the raw longreprtext

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/buildkite_test_collector/pytest_plugin/failure_reasons.py New module implementing failure reason parsing logic for different PyTest longrepr formats
src/buildkite_test_collector/collector/payload.py Added failure_expanded field to TestResultFailed and updated JSON serialization
src/buildkite_test_collector/pytest_plugin/buildkite_plugin.py Updated to use new failure parsing logic and added debug logging
tests/buildkite_test_collector/pytest_plugin/test_plugin.py Added comprehensive tests for different failure scenarios
tests/buildkite_test_collector/conftest.py Updated test fixture to include failure_expanded data
tests/buildkite_test_collector/collector/test_payload.py Updated test assertions to verify failure_expanded field

case None:
return None, None

case str() as s:
Copy link

Copilot AI Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The match case pattern 'str() as s' is redundant. Consider using 'case str(s):' or 'case s if isinstance(s, str):' for better readability.

Suggested change
case str() as s:
case str(s):

Copilot uses AI. Check for mistakes.
case str() as s:
lines = s.splitlines()
failure_reason = lines[0] if lines else s
return failure_reason, [{"expanded": lines[1:]}]
Copy link

Copilot AI Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When there's only one line in the string, this will return an empty list for 'expanded'. Consider returning None for failure_expanded when there are no additional lines, similar to the single-line case at the end of the function.

Suggested change
return failure_reason, [{"expanded": lines[1:]}]
return failure_reason, [{"expanded": lines[1:]}] if len(lines) > 1 else (failure_reason, None)

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@gchan gchan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, some minor questions

class TestResultFailed:
"""Represents a failed test result"""
failure_reason: Optional[str]
failure_expanded: Optional[Iterable[Mapping[str, Iterable[str]]]] = None
Copy link
Contributor

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 :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if isinstance(longrepr, ExceptionRepr) and longrepr.reprcrash is not None:
return _handle_exception_repr_longrepr(longrepr)

return _handle_default_longrepr(longrepr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand Pytest's longrepr.

Do we need to handle when it's a TerminalRepr?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That can be handled by the default _ handler, which just uses str(longrepr) and comes out okay.

msg: str
) -> tuple[str | None, Iterable[Mapping[str, Iterable[str]]] | None]:
"""Handle tuple longrepr case (path, line, msg)"""
failure_reason = msg
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any chance the tuple could have a very long message?

Copy link
Member Author

Choose a reason for hiding this comment

The 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 longrepr is for a skipped test, not a failed test, but I'm not entirely sure.

@@ -0,0 +1,113 @@
"""Buildkite Test Engine PyTest failure reason mapping"""

from __future__ import annotations
Copy link
Member Author

Choose a reason for hiding this comment

The 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.

@pda pda merged commit 99c7d05 into main Jul 21, 2025
11 checks passed
@pda pda deleted the failure-expanded branch July 21, 2025 01:26
@pda pda mentioned this pull request Jul 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants