Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR enhances the pytest plugin to properly handle test failures that occur during the setup phase (e.g., fixture failures), which were previously resulting in "unknown" status without failure information.
Key changes include:
- Added support for capturing failures during the setup phase in addition to the call phase
- Introduced a new
failure_reasonsmodule to extract detailed failure information from pytest's longrepr - Enhanced
TestResultFailedto include expanded failure details with backtrace information
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/buildkite_test_collector/pytest_plugin/buildkite_plugin.py |
Modified to handle setup phase failures and refactored failure processing logic |
src/buildkite_test_collector/pytest_plugin/failure_reasons.py |
New module for extracting failure reasons and expanded details from pytest longrepr |
src/buildkite_test_collector/collector/payload.py |
Enhanced TestResultFailed to include failure_expanded field and updated serialization |
tests/buildkite_test_collector/pytest_plugin/test_plugin.py |
Added comprehensive tests for various failure scenarios including setup failures |
tests/buildkite_test_collector/conftest.py |
Updated failed_test fixture to include failure_expanded data |
tests/buildkite_test_collector/collector/test_payload.py |
Updated test expectations for new failure_expanded field |
| source = entry.getsource() if hasattr(entry, "getsource") else None | ||
| if source: | ||
| expanded.extend(str(line) for line in source) |
There was a problem hiding this comment.
Using hasattr for method existence checking followed by method call can be fragile. Consider using getattr(entry, 'getsource', None) or wrapping in a try-except block for more robust error handling.
| source = entry.getsource() if hasattr(entry, "getsource") else None | |
| if source: | |
| expanded.extend(str(line) for line in source) | |
| getsource_method = getattr(entry, "getsource", None) | |
| if callable(getsource_method): | |
| source = getsource_method() | |
| if source: | |
| expanded.extend(str(line) for line in source) |
| case ExceptionRepr() as er if er.reprcrash is not None: | ||
| failure_reason = er.reprcrash.message # e.g. "ZeroDivisionError: division by zero" |
There was a problem hiding this comment.
Direct attribute access to er.reprcrash.message without null checking could raise AttributeError if reprcrash is None. Consider adding a null check or using defensive programming.
| case ExceptionRepr() as er if er.reprcrash is not None: | |
| failure_reason = er.reprcrash.message # e.g. "ZeroDivisionError: division by zero" | |
| case ExceptionRepr() as er: | |
| if er.reprcrash is not None: | |
| failure_reason = er.reprcrash.message # e.g. "ZeroDivisionError: division by zero" | |
| else: | |
| failure_reason = None |
4ef4c42 to
8f8500a
Compare
Otherwise, these are left without a result or failure reason set on them, and come through as 'unknown'.
8f8500a to
057bf4b
Compare
|
|
||
| nodeid = report.nodeid | ||
| test_data = self.in_flight.get(nodeid) | ||
| logger.debug('Enter pytest_runtest_logreport for %s', nodeid) |
There was a problem hiding this comment.
Do we want to add a similar debug message to update_test_result?
There was a problem hiding this comment.
I mostly wanted to add it to the PyTest hooks themselves…
the update_test_result method does debug-log when it takes action…
logger.debug('-> test passed')
logger.debug('-> test skipped')
logger.debug('-> test failed: %s', failure_reason)
I think it's okay for it to remain silent when it doesn't do anything.
Also I wasn't sure I should even leave in as much debugging as I did.
I added it for my own purposes in the moment, and tried to make it a bit more consistent than it was, but maybe it should be pruned back down a bit. Not sure!
Currently, errors during
setup(e.g. fixtures) cause a result ofunknownwithout any error/failure information.For example:
Because the
setupphase failed, thecallphase never runs, and so the assertion never runs.This pull request adds handling for
report.when == 'setup' and report.failed, which captures this scenario.Related:
failure_reason&failure_expanded#64