diff --git a/AUTHORS b/AUTHORS index b8e6377fb02..e797f21461a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -313,6 +313,7 @@ Seth Junot Shantanu Jain Shubham Adep Simon Gomizelj +Simon Holesch Simon Kerr Skylar Downes Srinivas Reddy Thatiparthy diff --git a/changelog/10190.bugfix.rst b/changelog/10190.bugfix.rst new file mode 100644 index 00000000000..731d0efad9a --- /dev/null +++ b/changelog/10190.bugfix.rst @@ -0,0 +1 @@ +Invalid XML characters in setup or teardown error messages are now properly escaped for JUnit XML reports. diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index c92a227bf1e..66057ef6f61 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -231,7 +231,7 @@ def append_error(self, report: TestReport) -> None: msg = f'failed on teardown with "{reason}"' else: msg = f'failed on setup with "{reason}"' - self._add_simple("error", msg, str(report.longrepr)) + self._add_simple("error", bin_xml_escape(msg), str(report.longrepr)) def append_skipped(self, report: TestReport) -> None: if hasattr(report, "wasxfail"): diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 02531e81435..b266c76d922 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1625,6 +1625,28 @@ def test_skip(): snode.assert_attr(message="1 <> 2") +def test_escaped_setup_teardown_error( + pytester: Pytester, run_and_parse: RunAndParse +) -> None: + pytester.makepyfile( + """ + import pytest + + @pytest.fixture() + def my_setup(): + raise Exception("error: \033[31mred\033[m") + + def test_esc(my_setup): + pass + """ + ) + _, dom = run_and_parse() + node = dom.find_first_by_tag("testcase") + snode = node.find_first_by_tag("error") + assert "#x1B[31mred#x1B[m" in snode["message"] + assert "#x1B[31mred#x1B[m" in snode.text + + @parametrize_families def test_logging_passing_tests_disabled_does_not_log_test_output( pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str