diff --git a/changelog/10607.bugfix.rst b/changelog/10607.bugfix.rst new file mode 100644 index 000000000..e89504695 --- /dev/null +++ b/changelog/10607.bugfix.rst @@ -0,0 +1 @@ +Fix a race condition when creating junitxml reports, which could occur when multiple instances of pytest execute in parallel. diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 7a5170f32..9242d46d9 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -645,8 +645,8 @@ class LogXML: def pytest_sessionfinish(self) -> None: dirname = os.path.dirname(os.path.abspath(self.logfile)) - if not os.path.isdir(dirname): - os.makedirs(dirname) + # exist_ok avoids filesystem race conditions between checking path existence and requesting creation + os.makedirs(dirname, exist_ok=True) with open(self.logfile, "w", encoding="utf-8") as logfile: suite_stop_time = timing.time() diff --git a/testing/conftest.py b/testing/conftest.py index 107aad86b..8a9816799 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -157,6 +157,7 @@ def color_mapping(): "number": "\x1b[94m", "str": "\x1b[33m", "print": "\x1b[96m", + "endline": "\x1b[90m\x1b[39;49;00m", } RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()} diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index 6fe718b53..b5a04a99f 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -254,7 +254,7 @@ class TestTerminalWriterLineWidth: pytest.param( True, True, - "{kw}assert{hl-reset} {number}0{hl-reset}\n", + "{kw}assert{hl-reset} {number}0{hl-reset}{endline}\n", id="with markup and code_highlight", ), pytest.param( diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 9de9a85f0..453f18323 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1265,14 +1265,14 @@ def test_color_yes(pytester: Pytester, color_mapping) -> None: "=*= FAILURES =*=", "{red}{bold}_*_ test_this _*_{reset}", "", - " {kw}def{hl-reset} {function}test_this{hl-reset}():", - "> fail()", + " {kw}def{hl-reset} {function}test_this{hl-reset}():{endline}", + "> fail(){endline}", "", "{bold}{red}test_color_yes.py{reset}:5: ", "_ _ * _ _*", "", - " {kw}def{hl-reset} {function}fail{hl-reset}():", - "> {kw}assert{hl-reset} {number}0{hl-reset}", + " {kw}def{hl-reset} {function}fail{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", "", "{bold}{red}test_color_yes.py{reset}:2: AssertionError", @@ -1292,9 +1292,9 @@ def test_color_yes(pytester: Pytester, color_mapping) -> None: "=*= FAILURES =*=", "{red}{bold}_*_ test_this _*_{reset}", "{bold}{red}test_color_yes.py{reset}:5: in test_this", - " fail()", + " fail(){endline}", "{bold}{red}test_color_yes.py{reset}:2: in fail", - " {kw}assert{hl-reset} {number}0{hl-reset}", + " {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", "{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}", ] @@ -2472,8 +2472,8 @@ class TestCodeHighlight: result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", - "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}", + " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}", "{bold}{red}E assert 1 == 10{reset}", ] ) @@ -2494,9 +2494,9 @@ class TestCodeHighlight: result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", + " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", " {print}print{hl-reset}({str}'''{hl-reset}{str}{hl-reset}", - "> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}", + "> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", ] ) @@ -2517,8 +2517,8 @@ class TestCodeHighlight: result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", - "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}", + " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}", "{bold}{red}E assert 1 == 10{reset}", ] )