From ab331c906e9047383eee11f4929b7edefe82b63e Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <nicoddemus@gmail.com>
Date: Thu, 11 Jun 2020 16:47:59 -0300
Subject: [PATCH] Suppress errors while removing tmpdir's lock files

Fix #5456
---
 changelog/5456.bugfix.rst |  2 ++
 src/_pytest/pathlib.py    | 13 +++++++++----
 testing/test_pathlib.py   | 21 +++++++++++++++++++++
 3 files changed, 32 insertions(+), 4 deletions(-)
 create mode 100644 changelog/5456.bugfix.rst

diff --git a/changelog/5456.bugfix.rst b/changelog/5456.bugfix.rst
new file mode 100644
index 000000000..176807570
--- /dev/null
+++ b/changelog/5456.bugfix.rst
@@ -0,0 +1,2 @@
+Fix a possible race condition when trying to remove lock files used to control access to folders
+created by ``tmp_path`` and ``tmpdir``.
diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py
index 29d8c4dc9..98ec936a1 100644
--- a/src/_pytest/pathlib.py
+++ b/src/_pytest/pathlib.py
@@ -1,4 +1,5 @@
 import atexit
+import contextlib
 import fnmatch
 import itertools
 import os
@@ -290,10 +291,14 @@ def ensure_deletable(path: Path, consider_lock_dead_if_created_before: float) ->
         return False
     else:
         if lock_time < consider_lock_dead_if_created_before:
-            lock.unlink()
-            return True
-        else:
-            return False
+            # wa want to ignore any errors while trying to remove the lock such as:
+            # - PermissionDenied, like the file permissions have changed since the lock creation
+            # - FileNotFoundError, in case another pytest process got here first.
+            # and any other cause of failure.
+            with contextlib.suppress(OSError):
+                lock.unlink()
+                return True
+        return False
 
 
 def try_cleanup(path: Path, consider_lock_dead_if_created_before: float) -> None:
diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py
index 03bed26ec..acc963199 100644
--- a/testing/test_pathlib.py
+++ b/testing/test_pathlib.py
@@ -1,9 +1,11 @@
 import os.path
 import sys
+import unittest.mock
 
 import py
 
 import pytest
+from _pytest.pathlib import ensure_deletable
 from _pytest.pathlib import fnmatch_ex
 from _pytest.pathlib import get_extended_length_path_str
 from _pytest.pathlib import get_lock_path
@@ -113,3 +115,22 @@ def test_get_extended_length_path_str():
     assert get_extended_length_path_str(r"\\share\foo") == r"\\?\UNC\share\foo"
     assert get_extended_length_path_str(r"\\?\UNC\share\foo") == r"\\?\UNC\share\foo"
     assert get_extended_length_path_str(r"\\?\c:\foo") == r"\\?\c:\foo"
+
+
+def test_suppress_error_removing_lock(tmp_path):
+    """ensure_deletable should not raise an exception if the lock file cannot be removed (#5456)"""
+    path = tmp_path / "dir"
+    path.mkdir()
+    lock = get_lock_path(path)
+    lock.touch()
+    mtime = lock.stat().st_mtime
+
+    with unittest.mock.patch.object(Path, "unlink", side_effect=OSError):
+        assert not ensure_deletable(
+            path, consider_lock_dead_if_created_before=mtime + 30
+        )
+    assert lock.is_file()
+
+    # check now that we can remove the lock file in normal circumstances
+    assert ensure_deletable(path, consider_lock_dead_if_created_before=mtime + 30)
+    assert not lock.is_file()