Do not update cache from xdist worker (#10641)
This commit is contained in:
parent
3ad4344656
commit
8efb4bb9c1
1
AUTHORS
1
AUTHORS
|
@ -315,6 +315,7 @@ Samuel Searles-Bryant
|
||||||
Samuele Pedroni
|
Samuele Pedroni
|
||||||
Sanket Duthade
|
Sanket Duthade
|
||||||
Sankt Petersbug
|
Sankt Petersbug
|
||||||
|
Saravanan Padmanaban
|
||||||
Segev Finer
|
Segev Finer
|
||||||
Serhii Mozghovyi
|
Serhii Mozghovyi
|
||||||
Seth Junot
|
Seth Junot
|
||||||
|
|
|
@ -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.
|
|
@ -48,6 +48,10 @@ def pytest_configure(config: Config) -> None:
|
||||||
def pytest_sessionfinish(session: Session) -> None:
|
def pytest_sessionfinish(session: Session) -> None:
|
||||||
if not session.config.getoption("stepwise"):
|
if not session.config.getoption("stepwise"):
|
||||||
assert session.config.cache is not None
|
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.
|
# Clear the list of failing tests if the plugin is not active.
|
||||||
session.config.cache.set(STEPWISE_CACHE_DIR, [])
|
session.config.cache.set(STEPWISE_CACHE_DIR, [])
|
||||||
|
|
||||||
|
@ -119,4 +123,8 @@ class StepwisePlugin:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def pytest_sessionfinish(self) -> 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)
|
self.cache.set(STEPWISE_CACHE_DIR, self.lastfailed)
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from _pytest.cacheprovider import Cache
|
||||||
from _pytest.monkeypatch import MonkeyPatch
|
from _pytest.monkeypatch import MonkeyPatch
|
||||||
from _pytest.pytester import Pytester
|
from _pytest.pytester import Pytester
|
||||||
|
from _pytest.stepwise import STEPWISE_CACHE_DIR
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -278,3 +282,76 @@ def test_stepwise_skip_is_independent(pytester: Pytester) -> None:
|
||||||
def test_sw_skip_help(pytester: Pytester) -> None:
|
def test_sw_skip_help(pytester: Pytester) -> None:
|
||||||
result = pytester.runpytest("-h")
|
result = pytester.runpytest("-h")
|
||||||
result.stdout.fnmatch_lines("*Implicitly enables --stepwise.")
|
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
|
||||||
|
|
Loading…
Reference in New Issue