Merge pull request #10656 from pytest-dev/backport-10641-to-7.2.x
[7.2.x] Dont update cache from xdist worker
This commit is contained in:
		
						commit
						cb07711846
					
				
							
								
								
									
										1
									
								
								AUTHORS
								
								
								
								
							
							
						
						
									
										1
									
								
								AUTHORS
								
								
								
								
							| 
						 | 
					@ -311,6 +311,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