Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/buildkite_test_collector/pytest_plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ def pytest_unconfigure(config):

if plugin:
api = API(os.environ)
numprocesses = config.getoption("numprocesses")
xdist_plugin = config.pluginmanager.getplugin("xdist")
if xdist_plugin is not None:
numprocesses = config.getoption("numprocesses")
else:
numprocesses = None
xdist_enabled = (
config.pluginmanager.getplugin("xdist") is not None
xdist_plugin is not None
and numprocesses is not None
and numprocesses > 0
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ def pytest_runtest_logreport(self, report):

# This hook is called three times during the lifecycle of a test:
# after the setup phase, the call phase, and the teardown phase.
# We capture outcomes from the call phase, or setup phase if it failed
# (since setup failures prevent the call phase from running).
# We capture outcomes from the call phase, or setup/teardown phase if it failed
# (since setup failures prevent the call phase from running, and teardown
# failures should mark an otherwise passing test as failed).
# See: https://github.com/buildkite/test-collector-python/pull/45
if report.when == 'call' or (report.when == 'setup' and report.failed):
# See: https://github.com/buildkite/test-collector-python/issues/84
if report.when == 'call' or (report.when in ('setup', 'teardown') and report.failed):
self.update_test_result(report)

# This hook only runs in xdist worker thread, not controller thread.
Expand Down
33 changes: 33 additions & 0 deletions tests/buildkite_test_collector/pytest_plugin/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,39 @@ def test_pytest_runtest_logreport_fail_exception_in_setup(fake_env):
assert len(fe["backtrace"]) > 0


def test_pytest_runtest_logreport_fail_exception_in_teardown(fake_env):
"""Teardown failure should override a passed call result"""
payload = Payload.init(fake_env)
plugin = BuildkitePlugin(payload)

location = ("", None, "")

# First, simulate a passing call phase
call_report = TestReport(nodeid="", location=location, keywords={}, outcome="passed", longrepr=None, when="call")
plugin.pytest_runtest_logstart(call_report.nodeid, location)
plugin.pytest_runtest_logreport(call_report)

# Verify it's marked as passed after call
test_data = plugin.in_flight.get(call_report.nodeid)
assert isinstance(test_data.result, TestResultPassed)

# Now simulate a failing teardown phase
try:
raise Exception("a fake teardown exception")
except Exception as e:
longrepr = ExceptionInfo.from_exception(e)
teardown_report = TestReport(nodeid="", location=location, keywords={}, outcome="failed", longrepr=longrepr, when="teardown")

plugin.pytest_runtest_logreport(teardown_report)
test_data = plugin.in_flight.get(teardown_report.nodeid)
plugin.pytest_runtest_logfinish(teardown_report.nodeid, location)

# Verify teardown failure overrides the passed result
assert isinstance(test_data, TestData)
assert isinstance(test_data.result, TestResultFailed)
assert test_data.result.failure_reason == "Exception: a fake teardown exception"


def test_pytest_runtest_logreport_simple_skip(fake_env):
payload = Payload.init(fake_env)
plugin = BuildkitePlugin(payload)
Expand Down