diff --git a/changelog/4181.bugfix.rst b/changelog/4181.bugfix.rst new file mode 100644 index 00000000000..668f8ba1e5e --- /dev/null +++ b/changelog/4181.bugfix.rst @@ -0,0 +1 @@ +Handle race condition between creation and deletion of temporary folders. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index b3519c682a2..ba9dcdfb312 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -185,9 +185,15 @@ def cleanup_on_exit(lock_path=lock_path, original_pid=pid): return register(cleanup_on_exit) -def delete_a_numbered_dir(path): - """removes a numbered directory""" - create_cleanup_lock(path) +def maybe_delete_a_numbered_dir(path): + """removes a numbered directory if its lock can be obtained""" + try: + create_cleanup_lock(path) + except (OSError, EnvironmentError): + # known races: + # * other process did a cleanup at the same time + # * deletable folder was found + return parent = path.parent garbage = parent.joinpath("garbage-{}".format(uuid.uuid4())) @@ -217,7 +223,7 @@ def ensure_deletable(path, consider_lock_dead_if_created_before): def try_cleanup(path, consider_lock_dead_if_created_before): """tries to cleanup a folder if we can ensure its deletable""" if ensure_deletable(path, consider_lock_dead_if_created_before): - delete_a_numbered_dir(path) + maybe_delete_a_numbered_dir(path) def cleanup_candidates(root, prefix, keep): diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index d0bb3881ab9..39e5bc44386 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -7,6 +7,7 @@ import six import pytest +from _pytest import pathlib from _pytest.pathlib import Path @@ -287,11 +288,17 @@ def test_rmtree(self, tmp_path): rmtree(adir, force=True) assert not adir.exists() - def test_cleanup_symlink(self, tmp_path): + def test_cleanup_ignores_symlink(self, tmp_path): the_symlink = tmp_path / (self.PREFIX + "current") attempt_symlink_to(the_symlink, tmp_path / (self.PREFIX + "5")) self._do_cleanup(tmp_path) + def test_removal_accepts_lock(self, tmp_path): + folder = pathlib.make_numbered_dir(root=tmp_path, prefix=self.PREFIX) + pathlib.create_cleanup_lock(folder) + pathlib.maybe_delete_a_numbered_dir(folder) + assert folder.is_dir() + def attempt_symlink_to(path, to_path): """Try to make a symlink from "path" to "to_path", skipping in case this platform