From 0084fd9783ed6b699903f6f636adafa09bade25f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 23 Oct 2019 19:22:26 -0300 Subject: [PATCH] Review rm_rf handling of FileNotFoundErrors (#6044) Review rm_rf handling of FileNotFoundErrors Conflicts: src/_pytest/pathlib.py testing/test_tmpdir.py --- changelog/6044.bugfix.rst | 3 +++ src/_pytest/pathlib.py | 27 +++++++++++++++++++++------ testing/test_tmpdir.py | 7 ++++++- 3 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 changelog/6044.bugfix.rst diff --git a/changelog/6044.bugfix.rst b/changelog/6044.bugfix.rst new file mode 100644 index 00000000000..575bd979649 --- /dev/null +++ b/changelog/6044.bugfix.rst @@ -0,0 +1,3 @@ +Properly ignore ``FileNotFoundError`` (``OSError.errno == NOENT`` in Python 2) exceptions when trying to remove old temporary directories, +for instance when multiple processes try to remove the same directory (common with ``pytest-xdist`` +for example). diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index 8d121619444..42071f43104 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -48,24 +48,38 @@ def ensure_reset_dir(path): def on_rm_rf_error(func, path, exc, **kwargs): - """Handles known read-only errors during rmtree.""" + """Handles known read-only errors during rmtree. + + The returned value is used only by our own tests. + """ start_path = kwargs["start_path"] - excvalue = exc[1] + exctype, excvalue = exc[:2] + + # another process removed the file in the middle of the "rm_rf" (xdist for example) + # more context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018 + if isinstance(excvalue, OSError) and excvalue.errno == errno.ENOENT: + return False if not isinstance(excvalue, OSError) or excvalue.errno not in ( errno.EACCES, errno.EPERM, ): warnings.warn( - PytestWarning("(rm_rf) error removing {}: {}".format(path, excvalue)) + PytestWarning( + "(rm_rf) error removing {}\n{}: {}".format(path, exctype, excvalue) + ) ) - return + return False if func not in (os.rmdir, os.remove, os.unlink): warnings.warn( - PytestWarning("(rm_rf) error removing {}: {}".format(path, excvalue)) + PytestWarning( + "(rm_rf) unknown function {} when removing {}:\n{}: {}".format( + path, func, exctype, excvalue + ) + ) ) - return + return False # Chmod + retry. import stat @@ -86,6 +100,7 @@ def chmod_rw(p): chmod_rw(str(path)) func(path) + return True def rm_rf(path): diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 4ed5df21647..d5291f5a9e7 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -398,9 +398,14 @@ def test_on_rm_rf_error(self, tmp_path): on_rm_rf_error(os.unlink, str(fn), exc_info, start_path=tmp_path) assert fn.is_file() + # we ignore FileNotFoundError + file_not_found = OSError() + file_not_found.errno = errno.ENOENT + exc_info = (None, file_not_found, None) + assert not on_rm_rf_error(None, str(fn), exc_info, start_path=tmp_path) + permission_error = OSError() permission_error.errno = errno.EACCES - # unknown function with pytest.warns(pytest.PytestWarning): exc_info = (None, permission_error, None)