Skip to content
Draft
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
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Clément M.T. Robert
Cornelius Riemenschneider
CrazyMerlyn
Cristian Vera
cty-ut
Cyrus Maden
Daara Shaw
Damian Skrzypczak
Expand Down
1 change: 1 addition & 0 deletions changelog/15196.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed missing captured output in JUnit XML reports for setup errors when ``junit_log_passing_tests`` is disabled.
7 changes: 6 additions & 1 deletion src/_pytest/junitxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,12 @@ def _add_simple(self, tag: str, message: str, data: str | None = None) -> None:
self.append(node)

def write_captured_output(self, report: TestReport) -> None:
if not self.xml.log_passing_tests and report.passed:
# A passing teardown does not make a test with a setup error pass.
if (
not self.xml.log_passing_tests
and report.passed
and not any(node.tag == "error" for node in self.nodes)
):
return

content_out = report.capstdout
Expand Down
56 changes: 56 additions & 0 deletions testing/test_junitxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -1894,6 +1894,62 @@ def test_func():
assert len(node.find_by_tag("system-out")) == 0


@parametrize_families
@pytest.mark.parametrize("log_passing_tests", [True, False])
@pytest.mark.parametrize("teardown_error", [True, False])
def test_setup_error_keeps_captured_output(
pytester: Pytester,
run_and_parse: RunAndParse,
xunit_family: _JunitFamily,
log_passing_tests: bool,
teardown_error: bool,
) -> None:
pytester.makepyfile(
f"""
import logging
import sys
import pytest

def emit(phase):
print(phase + " stdout")
print(phase + " stderr", file=sys.stderr)
logging.warning(phase + " log")

@pytest.fixture
def broken_setup(request):
def cleanup():
emit("teardown")
if {teardown_error!r}:
raise RuntimeError("teardown failed")

request.addfinalizer(cleanup)
emit("setup")
raise RuntimeError("setup failed")

def test_example(broken_setup):
pass
"""
)
result, dom = run_and_parse(
"-o",
"junit_logging=all",
"-o",
f"junit_log_passing_tests={log_passing_tests}",
family=xunit_family,
)
result.assert_outcomes(errors=2 if teardown_error else 1)
testcase = dom.get_first_by_tag("testcase")
# Both phases' output should be retained exactly once, even if cleanup fails.
stdout_nodes = testcase.find_by_tag("system-out")
stderr_nodes = testcase.find_by_tag("system-err")
assert len(stdout_nodes) == 1
assert len(stderr_nodes) == 1
for phase in ("setup", "teardown"):
assert stdout_nodes[0].text.count(f"{phase} stdout") == 1
assert stdout_nodes[0].text.count(f"{phase} log") == 1
assert stderr_nodes[0].text.count(f"{phase} stderr") == 1


def test_no_message_quiet(pytester: Pytester) -> None:
"""Do not show the summary banner when --quiet is given (#13700)."""
pytester.makepyfile("def test(): pass")
Expand Down
Loading