Fix rmtree to remove directories with read-only files (#5588)

Fix rmtree to remove directories with read-only files
This commit is contained in:
Bruno Oliveira
2019-07-11 18:57:03 -03:00
parent 01655b114e
commit 02c737fe4e
4 changed files with 99 additions and 14 deletions

View File

@@ -21,7 +21,7 @@ import pytest
from .compat import _PY2 as PY2
from .pathlib import Path
from .pathlib import resolve_from_str
from .pathlib import rmtree
from .pathlib import rm_rf
README_CONTENT = u"""\
# pytest cache directory #
@@ -51,7 +51,7 @@ class Cache(object):
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)

View File

@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import atexit
import errno
import fnmatch
@@ -8,6 +10,7 @@ import os
import shutil
import sys
import uuid
import warnings
from functools import reduce
from os.path import expanduser
from os.path import expandvars
@@ -19,6 +22,7 @@ import six
from six.moves import map
from .compat import PY36
from _pytest.warning_types import PytestWarning
if PY36:
from pathlib import Path, PurePath
@@ -38,17 +42,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):
@@ -186,7 +215,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