Skip to content

Commit ff84a74

Browse files
authored
Merge pull request #1739 from EliahKagan/rmtree
Make the rmtree callback Windows-only
2 parents 74cc671 + b226f3d commit ff84a74

File tree

2 files changed

+50
-17
lines changed

2 files changed

+50
-17
lines changed

git/util.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,9 @@ def handler(function: Callable, path: PathLike, _excinfo: Any) -> None:
219219
raise SkipTest(f"FIXME: fails with: PermissionError\n {ex}") from ex
220220
raise
221221

222-
if sys.version_info >= (3, 12):
222+
if os.name != "nt":
223+
shutil.rmtree(path)
224+
elif sys.version_info >= (3, 12):
223225
shutil.rmtree(path, onexc=handler)
224226
else:
225227
shutil.rmtree(path, onerror=handler)

test/test_util.py

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -109,31 +109,66 @@ def test_deletes_dir_with_readonly_files(self, tmp_path):
109109
sys.platform == "cygwin",
110110
reason="Cygwin can't set the permissions that make the test meaningful.",
111111
)
112-
def test_wraps_perm_error_if_enabled(self, mocker, permission_error_tmpdir):
113-
"""rmtree wraps PermissionError when HIDE_WINDOWS_KNOWN_ERRORS is true."""
112+
def test_avoids_changing_permissions_outside_tree(self, tmp_path):
113+
# Automatically works on Windows, but on Unix requires either special handling
114+
# or refraining from attempting to fix PermissionError by making chmod calls.
115+
116+
dir1 = tmp_path / "dir1"
117+
dir1.mkdir()
118+
(dir1 / "file").write_bytes(b"")
119+
(dir1 / "file").chmod(stat.S_IRUSR)
120+
old_mode = (dir1 / "file").stat().st_mode
121+
122+
dir2 = tmp_path / "dir2"
123+
dir2.mkdir()
124+
(dir2 / "symlink").symlink_to(dir1 / "file")
125+
dir2.chmod(stat.S_IRUSR | stat.S_IXUSR)
126+
127+
try:
128+
rmtree(dir2)
129+
except PermissionError:
130+
pass # On Unix, dir2 is not writable, so dir2/symlink may not be deleted.
131+
except SkipTest as ex:
132+
self.fail(f"rmtree unexpectedly attempts skip: {ex!r}")
133+
134+
new_mode = (dir1 / "file").stat().st_mode
135+
assert old_mode == new_mode, f"Should stay {old_mode:#o}, became {new_mode:#o}."
136+
137+
def _patch_for_wrapping_test(self, mocker, hide_windows_known_errors):
114138
# Access the module through sys.modules so it is unambiguous which module's
115139
# attribute we patch: the original git.util, not git.index.util even though
116140
# git.index.util "replaces" git.util and is what "import git.util" gives us.
117-
mocker.patch.object(sys.modules["git.util"], "HIDE_WINDOWS_KNOWN_ERRORS", True)
141+
mocker.patch.object(sys.modules["git.util"], "HIDE_WINDOWS_KNOWN_ERRORS", hide_windows_known_errors)
118142

119-
# Disable common chmod functions so the callback can never fix the problem.
143+
# Disable common chmod functions so the callback can't fix a PermissionError.
120144
mocker.patch.object(os, "chmod")
121145
mocker.patch.object(pathlib.Path, "chmod")
122146

123-
# Now we can see how an intractable PermissionError is treated.
147+
@pytest.mark.skipif(
148+
os.name != "nt",
149+
reason="PermissionError is only ever wrapped on Windows",
150+
)
151+
def test_wraps_perm_error_if_enabled(self, mocker, permission_error_tmpdir):
152+
"""rmtree wraps PermissionError on Windows when HIDE_WINDOWS_KNOWN_ERRORS is true."""
153+
self._patch_for_wrapping_test(mocker, True)
154+
124155
with pytest.raises(SkipTest):
125156
rmtree(permission_error_tmpdir)
126157

127158
@pytest.mark.skipif(
128159
sys.platform == "cygwin",
129160
reason="Cygwin can't set the permissions that make the test meaningful.",
130161
)
131-
def test_does_not_wrap_perm_error_unless_enabled(self, mocker, permission_error_tmpdir):
132-
"""rmtree does not wrap PermissionError when HIDE_WINDOWS_KNOWN_ERRORS is false."""
133-
# See comments in test_wraps_perm_error_if_enabled for details about patching.
134-
mocker.patch.object(sys.modules["git.util"], "HIDE_WINDOWS_KNOWN_ERRORS", False)
135-
mocker.patch.object(os, "chmod")
136-
mocker.patch.object(pathlib.Path, "chmod")
162+
@pytest.mark.parametrize(
163+
"hide_windows_known_errors",
164+
[
165+
pytest.param(False),
166+
pytest.param(True, marks=pytest.mark.skipif(os.name == "nt", reason="We would wrap on Windows")),
167+
],
168+
)
169+
def test_does_not_wrap_perm_error_unless_enabled(self, mocker, permission_error_tmpdir, hide_windows_known_errors):
170+
"""rmtree does not wrap PermissionError on non-Windows systems or when HIDE_WINDOWS_KNOWN_ERRORS is false."""
171+
self._patch_for_wrapping_test(mocker, hide_windows_known_errors)
137172

138173
with pytest.raises(PermissionError):
139174
try:
@@ -144,11 +179,7 @@ def test_does_not_wrap_perm_error_unless_enabled(self, mocker, permission_error_
144179
@pytest.mark.parametrize("hide_windows_known_errors", [False, True])
145180
def test_does_not_wrap_other_errors(self, tmp_path, mocker, hide_windows_known_errors):
146181
file_not_found_tmpdir = tmp_path / "testdir" # It is deliberately never created.
147-
148-
# See comments in test_wraps_perm_error_if_enabled for details about patching.
149-
mocker.patch.object(sys.modules["git.util"], "HIDE_WINDOWS_KNOWN_ERRORS", hide_windows_known_errors)
150-
mocker.patch.object(os, "chmod")
151-
mocker.patch.object(pathlib.Path, "chmod")
182+
self._patch_for_wrapping_test(mocker, hide_windows_known_errors)
152183

153184
with pytest.raises(FileNotFoundError):
154185
try:

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy