summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelog/4181.bugfix.rst1
-rw-r--r--src/_pytest/pathlib.py14
-rw-r--r--testing/test_tmpdir.py9
3 files changed, 19 insertions, 5 deletions
diff --git a/changelog/4181.bugfix.rst b/changelog/4181.bugfix.rst
new file mode 100644
index 000000000..668f8ba1e
--- /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 b3519c682..ba9dcdfb3 100644
--- a/src/_pytest/pathlib.py
+++ b/src/_pytest/pathlib.py
@@ -185,9 +185,15 @@ def register_cleanup_lock_removal(lock_path, register=atexit.register):
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 d0bb3881a..39e5bc443 100644
--- a/testing/test_tmpdir.py
+++ b/testing/test_tmpdir.py
@@ -7,6 +7,7 @@ import sys
import six
import pytest
+from _pytest import pathlib
from _pytest.pathlib import Path
@@ -287,11 +288,17 @@ class TestNumberedDir(object):
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