Skip to content

Commit

Permalink
Do not update cache from xdist worker (#10641)
Browse files Browse the repository at this point in the history
  • Loading branch information
s-padmanaban committed Jan 13, 2023
1 parent 3ad4344 commit 8efb4bb
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 0 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -315,6 +315,7 @@ Samuel Searles-Bryant
Samuele Pedroni
Sanket Duthade
Sankt Petersbug
Saravanan Padmanaban
Segev Finer
Serhii Mozghovyi
Seth Junot
Expand Down
1 change: 1 addition & 0 deletions changelog/10641.bugfix.rst
@@ -0,0 +1 @@
Fix a race condition when creating or updating the stepwise plugin's cache, which could occur when multiple xdist worker nodes try to simultaneously update the stepwise plugin's cache.
8 changes: 8 additions & 0 deletions src/_pytest/stepwise.py
Expand Up @@ -48,6 +48,10 @@ def pytest_configure(config: Config) -> None:
def pytest_sessionfinish(session: Session) -> None:
if not session.config.getoption("stepwise"):
assert session.config.cache is not None
if hasattr(session.config, "workerinput"):
# Do not update cache if this process is a xdist worker to prevent
# race conditions (#10641).
return
# Clear the list of failing tests if the plugin is not active.
session.config.cache.set(STEPWISE_CACHE_DIR, [])

Expand Down Expand Up @@ -119,4 +123,8 @@ def pytest_report_collectionfinish(self) -> Optional[str]:
return None

def pytest_sessionfinish(self) -> None:
if hasattr(self.config, "workerinput"):
# Do not update cache if this process is a xdist worker to prevent
# race conditions (#10641).
return
self.cache.set(STEPWISE_CACHE_DIR, self.lastfailed)
77 changes: 77 additions & 0 deletions testing/test_stepwise.py
@@ -1,6 +1,10 @@
from pathlib import Path

import pytest
from _pytest.cacheprovider import Cache
from _pytest.monkeypatch import MonkeyPatch
from _pytest.pytester import Pytester
from _pytest.stepwise import STEPWISE_CACHE_DIR


@pytest.fixture
Expand Down Expand Up @@ -278,3 +282,76 @@ def test_three():
def test_sw_skip_help(pytester: Pytester) -> None:
result = pytester.runpytest("-h")
result.stdout.fnmatch_lines("*Implicitly enables --stepwise.")


def test_stepwise_xdist_dont_store_lastfailed(pytester: Pytester) -> None:
pytester.makefile(
ext=".ini",
pytest=f"[pytest]\ncache_dir = {pytester.path}\n",
)

pytester.makepyfile(
conftest="""
import pytest
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config) -> None:
config.workerinput = True
"""
)
pytester.makepyfile(
test_one="""
def test_one():
assert False
"""
)
result = pytester.runpytest("--stepwise")
assert result.ret == pytest.ExitCode.INTERRUPTED

stepwise_cache_file = (
pytester.path / Cache._CACHE_PREFIX_VALUES / STEPWISE_CACHE_DIR
)
assert not Path(stepwise_cache_file).exists()


def test_disabled_stepwise_xdist_dont_clear_cache(pytester: Pytester) -> None:
pytester.makefile(
ext=".ini",
pytest=f"[pytest]\ncache_dir = {pytester.path}\n",
)

stepwise_cache_file = (
pytester.path / Cache._CACHE_PREFIX_VALUES / STEPWISE_CACHE_DIR
)
stepwise_cache_dir = stepwise_cache_file.parent
stepwise_cache_dir.mkdir(exist_ok=True, parents=True)

stepwise_cache_file_relative = f"{Cache._CACHE_PREFIX_VALUES}/{STEPWISE_CACHE_DIR}"

expected_value = '"test_one.py::test_one"'
content = {f"{stepwise_cache_file_relative}": expected_value}

pytester.makefile(ext="", **content)

pytester.makepyfile(
conftest="""
import pytest
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config) -> None:
config.workerinput = True
"""
)
pytester.makepyfile(
test_one="""
def test_one():
assert True
"""
)
result = pytester.runpytest()
assert result.ret == 0

assert Path(stepwise_cache_file).exists()
with stepwise_cache_file.open() as file_handle:
observed_value = file_handle.readlines()
assert [expected_value] == observed_value

0 comments on commit 8efb4bb

Please sign in to comment.