Skip to content

Commit

Permalink
Fix rmtree_errorhandler to skip non-existing dirs
Browse files Browse the repository at this point in the history
Fixes pypa#4910.
  • Loading branch information
atugushev committed Sep 4, 2019
1 parent d764181 commit ba47721
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 1 deletion.
8 changes: 7 additions & 1 deletion src/pip/_internal/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,14 @@ def rmtree_errorhandler(func, path, exc_info):
"""On Windows, the files in .svn are read-only, so when rmtree() tries to
remove them, an exception is thrown. We catch that here, remove the
read-only attribute, and hopefully continue without problems."""
try:
is_readonly = os.stat(path).st_mode & stat.S_IREAD
except (IOError, OSError):
# Since the path already removed we don't raise the error
return

# if file type currently read only
if os.stat(path).st_mode & stat.S_IREAD:
if is_readonly:
# convert to read/write
os.chmod(path, stat.S_IWRITE)
# use the original function to repeat the operation
Expand Down
56 changes: 56 additions & 0 deletions tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
redact_netloc,
remove_auth_from_url,
rmtree,
rmtree_errorhandler,
split_auth_from_netloc,
split_auth_netloc_from_url,
untar_file,
Expand Down Expand Up @@ -386,6 +387,61 @@ def test_unpack_zip(self, data):
self.confirm_files()


def test_rmtree_errorhandler_not_existing_directory(tmpdir):
"""
Test `rmtree_errorhandler` ignores the given non-existing directory.
"""
not_existing_path = str(tmpdir / 'foo')
func_mock = Mock()
rmtree_errorhandler(func_mock, not_existing_path, None)
func_mock.assert_not_called()


def test_rmtree_errorhandler_readonly_directory(tmpdir):
"""
Test `rmtree_errorhandler` makes the given read-only directory writable.
"""
# Create read only directory
path = str((tmpdir / 'foo').mkdir())
os.chmod(path, stat.S_IREAD)

# Make sure func_mock is called with the given path
func_mock = Mock()
rmtree_errorhandler(func_mock, path, None)
func_mock.assert_called_with(path)

# Make sure the path is became writable
assert os.stat(path).st_mode & stat.S_IWRITE


def test_rmtree_errorhandler_reraises_error(tmpdir):
"""
Test `rmtree_errorhandler` re-raises an error
by the given non-readonly directory.
"""
# Create write only directory
path = str((tmpdir / 'foo').mkdir())
os.chmod(path, stat.S_IWRITE)

# Make sure the handler re-raises an exception.
# Note that the raise statement without expression and
# active exception in the current scope throws
# the RuntimeError on python3 and the TypeError on python2.
func_mock = Mock()
with pytest.raises((RuntimeError, TypeError)):
rmtree_errorhandler(func_mock, path, None)

func_mock.assert_not_called()


def test_rmtree_skips_not_existing_directory():
"""
Test wrapped `rmtree` doesn't raise an error
by the given non-existing directory.
"""
rmtree.__wrapped__('foo')


class Failer:
def __init__(self, duration=1):
self.succeed_after = time.time() + duration
Expand Down

0 comments on commit ba47721

Please sign in to comment.