Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Pierre-Jean Campigotto
Pierre-Luc Tessier Gagné
Prakhar Gurunani
Rahul Bangar
Robert Gomulka
Ronald Evers
Ronny Pfannschmidt
Ryuichi Ohori
Expand Down
1 change: 1 addition & 0 deletions docs/changelog/2498.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove read-only files in ``ensure_empty_dir``.
18 changes: 17 additions & 1 deletion src/tox/util/path.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
import errno
import os
import shutil
import stat

from tox import reporter


def ensure_empty_dir(path):
if path.check():
reporter.info(" removing {}".format(path))
shutil.rmtree(str(path), ignore_errors=True)
shutil.rmtree(str(path), onerror=_remove_readonly)
path.ensure(dir=1)


def _remove_readonly(func, path, exc_info):
"""Clear the readonly bit and reattempt the removal."""
if isinstance(exc_info[1], OSError):
if exc_info[1].errno == errno.EACCES:
try:
os.chmod(path, stat.S_IWRITE)
func(path)
except Exception:
# when second attempt fails, ignore the problem
# to maintain some level of backward compatibility
pass
19 changes: 19 additions & 0 deletions tests/integration/test_path_utils_removal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import os
from stat import S_IREAD

from tox.util.path import ensure_empty_dir


def test_remove_read_only(tmpdir):
nested_dir = tmpdir / "nested_dir"
nested_dir.mkdir()

# create read-only file
read_only_file = nested_dir / "tmpfile.txt"
with open(str(read_only_file), "w"):
pass
os.chmod(str(read_only_file), S_IREAD)

ensure_empty_dir(nested_dir)

assert not os.listdir(str(nested_dir))