From f233d5393fe360af50959d9e3c166e67aaecb469 Mon Sep 17 00:00:00 2001 From: robgom <38496047+robgom@users.noreply.github.com> Date: Sat, 17 Sep 2022 03:12:36 +0200 Subject: [PATCH] Remove read-only files upon cleanup (#2501) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- CONTRIBUTORS | 1 + docs/changelog/2498.bugfix.rst | 1 + src/tox/util/path.py | 18 +++++++++++++++++- tests/integration/test_path_utils_removal.py | 19 +++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/2498.bugfix.rst create mode 100644 tests/integration/test_path_utils_removal.py diff --git a/CONTRIBUTORS b/CONTRIBUTORS index d826e3be9..0eab06c58 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -107,6 +107,7 @@ Pierre-Jean Campigotto Pierre-Luc Tessier Gagné Prakhar Gurunani Rahul Bangar +Robert Gomulka Ronald Evers Ronny Pfannschmidt Ryuichi Ohori diff --git a/docs/changelog/2498.bugfix.rst b/docs/changelog/2498.bugfix.rst new file mode 100644 index 000000000..00673fbb9 --- /dev/null +++ b/docs/changelog/2498.bugfix.rst @@ -0,0 +1 @@ +Remove read-only files in ``ensure_empty_dir``. diff --git a/src/tox/util/path.py b/src/tox/util/path.py index b7a299810..07dd4b58e 100644 --- a/src/tox/util/path.py +++ b/src/tox/util/path.py @@ -1,4 +1,7 @@ +import errno +import os import shutil +import stat from tox import reporter @@ -6,5 +9,18 @@ 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 diff --git a/tests/integration/test_path_utils_removal.py b/tests/integration/test_path_utils_removal.py new file mode 100644 index 000000000..2fc46e7fd --- /dev/null +++ b/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))