Skip to content
Open
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
19 changes: 18 additions & 1 deletion src/xdist/dsession.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ def worker_workerfinished(self, node: WorkerController) -> None:
workerready before shutdown was triggered.
"""
self.config.hook.pytest_testnodedown(node=node, error=None)
if node.workeroutput["exitstatus"] == 2: # keyboard-interrupt
exitstatus = node.workeroutput["exitstatus"]
if exitstatus == 2: # keyboard-interrupt
self.shouldstop = f"{node} received keyboard-interrupt"
self.worker_errordown(node, "keyboard-interrupt")
return
Expand All @@ -214,6 +215,22 @@ def worker_workerfinished(self, node: WorkerController) -> None:
assert self.sched is not None
if node in self.sched.nodes:
crashitem = self.sched.remove_node(node)
# pytest.exit() with a custom exitcode can leave pending tests,
# causing crashitem to be non-empty. Handle this gracefully
# instead of raising INTERNALERROR.
if crashitem:
self.shouldstop = (
f"{node} exited with status {exitstatus}, "
f"pending test: {crashitem}"
)
self.worker_errordown(node, f"exit-{exitstatus}")
return
# For normal completion, crashitem should be empty.
# This assertion catches unexpected states.
# Note: exitstatus 0/1/5 are normal outcomes:
# 0: all tests passed
# 1: tests failed (but all were run)
# 5: no tests collected
assert not crashitem, (crashitem, node)
self._active_nodes.remove(node)

Expand Down
25 changes: 25 additions & 0 deletions testing/acceptance_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1699,3 +1699,28 @@ def test():
)
result = pytester.runpytest()
assert result.ret == 0


def test_pytest_exit_nonzero_exitcode(pytester: pytest.Pytester) -> None:
"""Test that pytest.exit() with non-zero exit code is handled properly.

Issue #1239: pytest.exit causes internal error when exit code is non-zero.
"""
pytester.makepyfile(
"""
import pytest
import time

@pytest.mark.parametrize('i', range(10))
def test_me(i):
if i == 5:
pytest.exit("Something", 1)
time.sleep(0.1)
"""
)
result = pytester.runpytest("-n2", "--tb=short")
# Should not cause INTERNALERROR, should exit cleanly
assert "INTERNALERROR" not in result.stdout.str()
# The worker exit status is handled, but xdist raises Interrupted
# which causes INTERRUPTED (2) exit status
assert result.ret == 2 # INTERRUPTED due to shouldstop handling
Loading