|
3 | 3 | """
|
4 | 4 |
|
5 | 5 | import contextlib
|
| 6 | +import errno |
| 7 | +import os |
6 | 8 | import unittest
|
| 9 | +from unittest import mock |
7 | 10 |
|
8 | 11 | from .support import is_pypi
|
9 | 12 | from .support.local_path import LocalPathGround
|
@@ -169,6 +172,74 @@ class LocalToLocalPathCopyTest(CopyTestBase, unittest.TestCase):
|
169 | 172 | source_ground = LocalPathGround(Path)
|
170 | 173 | target_ground = LocalPathGround(Path)
|
171 | 174 |
|
| 175 | + @unittest.skipUnless(os.name == 'nt', 'needs Windows for CopyFile2 fallback') |
| 176 | + def test_copy_hidden_file_fallback_on_access_denied(self): |
| 177 | + import _winapi |
| 178 | + import ctypes |
| 179 | + import pathlib |
| 180 | + |
| 181 | + if pathlib.copyfile2 is None: |
| 182 | + self.skipTest('copyfile2 unavailable') |
| 183 | + |
| 184 | + source = self.source_root / 'fileA' |
| 185 | + target = self.target_root / 'copy_hidden' |
| 186 | + |
| 187 | + kernel32 = ctypes.windll.kernel32 |
| 188 | + GetFileAttributesW = kernel32.GetFileAttributesW |
| 189 | + SetFileAttributesW = kernel32.SetFileAttributesW |
| 190 | + GetFileAttributesW.argtypes = [ctypes.c_wchar_p] |
| 191 | + GetFileAttributesW.restype = ctypes.c_uint32 |
| 192 | + SetFileAttributesW.argtypes = [ctypes.c_wchar_p, ctypes.c_uint32] |
| 193 | + SetFileAttributesW.restype = ctypes.c_int |
| 194 | + |
| 195 | + path_str = str(source) |
| 196 | + original_attrs = GetFileAttributesW(path_str) |
| 197 | + if original_attrs in (0xFFFFFFFF, ctypes.c_uint32(-1).value): |
| 198 | + self.skipTest('GetFileAttributesW failed') |
| 199 | + hidden_attrs = original_attrs | 0x2 # FILE_ATTRIBUTE_HIDDEN |
| 200 | + if not SetFileAttributesW(path_str, hidden_attrs): |
| 201 | + self.skipTest('SetFileAttributesW failed') |
| 202 | + self.addCleanup(SetFileAttributesW, path_str, original_attrs) |
| 203 | + |
| 204 | + def raise_access_denied(*args, **kwargs): |
| 205 | + exc = OSError(errno.EACCES, 'Access denied') |
| 206 | + exc.winerror = _winapi.ERROR_ACCESS_DENIED |
| 207 | + raise exc |
| 208 | + |
| 209 | + with mock.patch('pathlib.copyfile2', side_effect=raise_access_denied) as mock_copy: |
| 210 | + result = source.copy(target) |
| 211 | + |
| 212 | + self.assertEqual(result, target) |
| 213 | + self.assertTrue(self.target_ground.isfile(result)) |
| 214 | + self.assertEqual(self.source_ground.readbytes(source), |
| 215 | + self.target_ground.readbytes(result)) |
| 216 | + self.assertEqual(mock_copy.call_count, 1) |
| 217 | + |
| 218 | + @unittest.skipUnless(os.name == 'nt', 'needs Windows for CopyFile2 fallback') |
| 219 | + def test_copy_file_fallback_on_privilege_not_held(self): |
| 220 | + import _winapi |
| 221 | + import pathlib |
| 222 | + |
| 223 | + if pathlib.copyfile2 is None: |
| 224 | + self.skipTest('copyfile2 unavailable') |
| 225 | + |
| 226 | + source = self.source_root / 'fileA' |
| 227 | + target = self.target_root / 'copy_privilege' |
| 228 | + |
| 229 | + def raise_privilege_not_held(*args, **kwargs): |
| 230 | + exc = OSError(errno.EPERM, 'Privilege not held') |
| 231 | + exc.winerror = _winapi.ERROR_PRIVILEGE_NOT_HELD |
| 232 | + raise exc |
| 233 | + |
| 234 | + with mock.patch('pathlib.copyfile2', side_effect=raise_privilege_not_held) as mock_copy: |
| 235 | + result = source.copy(target) |
| 236 | + |
| 237 | + self.assertEqual(result, target) |
| 238 | + self.assertTrue(self.target_ground.isfile(result)) |
| 239 | + self.assertEqual(self.source_ground.readbytes(source), |
| 240 | + self.target_ground.readbytes(result)) |
| 241 | + self.assertEqual(mock_copy.call_count, 1) |
| 242 | + |
172 | 243 |
|
173 | 244 | if __name__ == "__main__":
|
174 | 245 | unittest.main()
|
0 commit comments