summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBruno Oliveira <bruno@esss.com.br>2019-07-10 09:52:23 -0300
committerBruno Oliveira <nicoddemus@gmail.com>2019-07-11 18:24:53 -0300
commit37c37963c45b40cb7ef00e4b3ae50b1701604d81 (patch)
treeff0dc088fecd2f53011c60c72033f5691789dd01 /src
parent2c402f4bd9cff2c6faeccb86a97364e1fa122a16 (diff)
downloadpytest-37c37963c45b40cb7ef00e4b3ae50b1701604d81.tar.gz
Fix rmtree to remove directories with read-only files
Fix #5524
Diffstat (limited to 'src')
-rwxr-xr-xsrc/_pytest/cacheprovider.py4
-rw-r--r--src/_pytest/pathlib.py45
2 files changed, 38 insertions, 11 deletions
diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py
index 17463959f..496931e0f 100755
--- a/src/_pytest/cacheprovider.py
+++ b/src/_pytest/cacheprovider.py
@@ -14,7 +14,7 @@ import py
import pytest
from .pathlib import Path
from .pathlib import resolve_from_str
-from .pathlib import rmtree
+from .pathlib import rm_rf
README_CONTENT = """\
# pytest cache directory #
@@ -44,7 +44,7 @@ class Cache:
def for_config(cls, config):
cachedir = cls.cache_dir_from_config(config)
if config.getoption("cacheclear") and cachedir.exists():
- rmtree(cachedir, force=True)
+ rm_rf(cachedir)
cachedir.mkdir()
return cls(cachedir, config)
diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py
index ecc38eb0f..7094d8c69 100644
--- a/src/_pytest/pathlib.py
+++ b/src/_pytest/pathlib.py
@@ -7,12 +7,14 @@ import os
import shutil
import sys
import uuid
+import warnings
from os.path import expanduser
from os.path import expandvars
from os.path import isabs
from os.path import sep
from posixpath import sep as posix_sep
+from _pytest.warning_types import PytestWarning
if sys.version_info[:2] >= (3, 6):
from pathlib import Path, PurePath
@@ -32,17 +34,42 @@ def ensure_reset_dir(path):
ensures the given path is an empty directory
"""
if path.exists():
- rmtree(path, force=True)
+ rm_rf(path)
path.mkdir()
-def rmtree(path, force=False):
- if force:
- # NOTE: ignore_errors might leave dead folders around.
- # Python needs a rm -rf as a followup.
- shutil.rmtree(str(path), ignore_errors=True)
- else:
- shutil.rmtree(str(path))
+def rm_rf(path):
+ """Remove the path contents recursively, even if some elements
+ are read-only.
+ """
+
+ def chmod_w(p):
+ import stat
+
+ mode = os.stat(str(p)).st_mode
+ os.chmod(str(p), mode | stat.S_IWRITE)
+
+ def force_writable_and_retry(function, p, excinfo):
+ p = Path(p)
+
+ # for files, we need to recursively go upwards
+ # in the directories to ensure they all are also
+ # writable
+ if p.is_file():
+ for parent in p.parents:
+ chmod_w(parent)
+ # stop when we reach the original path passed to rm_rf
+ if parent == path:
+ break
+
+ chmod_w(p)
+ try:
+ # retry the function that failed
+ function(str(p))
+ except Exception as e:
+ warnings.warn(PytestWarning("(rm_rf) error removing {}: {}".format(p, e)))
+
+ shutil.rmtree(str(path), onerror=force_writable_and_retry)
def find_prefixed(root, prefix):
@@ -168,7 +195,7 @@ def maybe_delete_a_numbered_dir(path):
garbage = parent.joinpath("garbage-{}".format(uuid.uuid4()))
path.rename(garbage)
- rmtree(garbage, force=True)
+ rm_rf(garbage)
except (OSError, EnvironmentError):
# known races:
# * other process did a cleanup at the same time