diff --git a/src/_pytest/callback.py b/src/_pytest/callback.py new file mode 100644 index 000000000..824e023d7 --- /dev/null +++ b/src/_pytest/callback.py @@ -0,0 +1,16 @@ +from typing import Any +from typing import Callable +from typing import List +from typing import Tuple + + +class Callback: + def __init__(self) -> None: + self._funcs: List[Tuple[Callable[..., Any], Any, Any]] = [] + + def register(self, func: Callable[..., Any], *args: Any, **kwargs: Any) -> None: + self._funcs.append((func, args, kwargs)) + + def execute(self) -> None: + for func, args, kwargs in reversed(self._funcs): + func(*args, **kwargs) diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 533335c66..8ebcaef85 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -362,6 +362,7 @@ def make_numbered_dir_with_cleanup( keep: int, lock_timeout: float, mode: int, + register=atexit.register, ) -> Path: """Create a numbered dir with a cleanup lock and remove old ones.""" e = None @@ -371,13 +372,13 @@ def make_numbered_dir_with_cleanup( # Only lock the current dir when keep is not 0 if keep != 0: lock_path = create_cleanup_lock(p) - register_cleanup_lock_removal(lock_path) + register_cleanup_lock_removal(lock_path, register=register) except Exception as exc: e = exc else: consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout # Register a cleanup for program exit - atexit.register( + register( cleanup_numbered_dir, root, prefix, diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 48670b6c4..1f9d2ba33 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -37,6 +37,7 @@ from _pytest.deprecated import check_ispytest from _pytest.fixtures import fixture from _pytest.fixtures import FixtureRequest from _pytest.monkeypatch import MonkeyPatch +from _pytest.callback import Callback tmppath_result_key = StashKey[Dict[str, bool]]() @@ -77,6 +78,7 @@ class TempPathFactory: self._retention_count = retention_count self._retention_policy = retention_policy self._basetemp = basetemp + self._lock_and_dir_removal_callback = Callback() @classmethod def from_config( @@ -196,6 +198,7 @@ class TempPathFactory: keep=keep, lock_timeout=LOCK_TIMEOUT, mode=0o700, + register=self._lock_and_dir_removal_callback.register, ) assert basetemp is not None, basetemp self._basetemp = basetemp @@ -315,6 +318,8 @@ def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]): # permissions, etc, in which case we ignore it. rmtree(passed_dir, ignore_errors=True) + tmp_path_factory._lock_and_dir_removal_callback.execute() + @hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item: Item, call):