Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove read-only files upon cleanup #2501

Merged
merged 10 commits into from Sep 17, 2022
1 change: 1 addition & 0 deletions CONTRIBUTORS
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
@@ -0,0 +1 @@
Remove read-only files in ``ensure_empty_dir``.
18 changes: 17 additions & 1 deletion src/tox/util/path.py
@@ -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
@@ -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))