Skip to content

Commit 4d81bd3

Browse files
committed
GH-130614: pathlib ABCs: parametrize test suite for path copying
Test copying from `Path` and `ReadableZipPath` (types of `_ReadablePath`) to `Path` and `WritableZipPath` (types of `_WritablePath`).
1 parent db6a998 commit 4d81bd3

File tree

3 files changed

+173
-139
lines changed

3 files changed

+173
-139
lines changed

Lib/pathlib/_os.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ def copy_file(source, target, follow_symlinks=True, preserve_metadata=False):
248248
"""
249249
info = source.info
250250
if not follow_symlinks and info.is_symlink():
251-
target.symlink_to(source.readlink(), info.is_dir())
251+
target.symlink_to(str(source.readlink()), info.is_dir())
252252
if preserve_metadata:
253253
target._write_info(info, follow_symlinks=False)
254254
elif info.is_dir():

Lib/test/test_pathlib/test_copy.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
"""
2+
Tests for copying from pathlib.types._ReadablePath to _WritablePath.
3+
"""
4+
5+
import contextlib
6+
import unittest
7+
8+
from pathlib import Path
9+
10+
from test.test_pathlib.support.local_path import LocalPathGround, WritableLocalPath
11+
from test.test_pathlib.support.zip_path import ZipPathGround, ReadableZipPath, WritableZipPath
12+
13+
14+
class CopyTestBase:
15+
def setUp(self):
16+
self.source_root = self.source_ground.setup()
17+
self.source_ground.create_hierarchy(self.source_root)
18+
self.target_root = self.target_ground.setup(local_suffix="_target")
19+
20+
def tearDown(self):
21+
self.source_ground.teardown(self.source_root)
22+
self.target_ground.teardown(self.target_root)
23+
24+
def test_copy_file(self):
25+
source = self.source_root / 'fileA'
26+
target = self.target_root / 'copyA'
27+
result = source.copy(target)
28+
self.assertEqual(result, target)
29+
self.assertTrue(self.target_ground.isfile(target))
30+
self.assertEqual(self.source_ground.readbytes(source),
31+
self.target_ground.readbytes(result))
32+
33+
def test_copy_file_empty(self):
34+
source = self.source_root / 'empty'
35+
target = self.target_root / 'copyA'
36+
self.source_ground.create_file(source, b'')
37+
result = source.copy(target)
38+
self.assertEqual(result, target)
39+
self.assertTrue(self.target_ground.isfile(target))
40+
self.assertEqual(self.target_ground.readbytes(result), b'')
41+
42+
def test_copy_file_to_existing_file(self):
43+
source = self.source_root / 'fileA'
44+
target = self.target_root / 'copyA'
45+
self.target_ground.create_file(target, b'this is a copy\n')
46+
with contextlib.ExitStack() as stack:
47+
if isinstance(target, WritableZipPath):
48+
stack.enter_context(self.assertWarns(UserWarning))
49+
result = source.copy(target)
50+
self.assertEqual(result, target)
51+
self.assertTrue(self.target_ground.isfile(target))
52+
self.assertEqual(self.source_ground.readbytes(source),
53+
self.target_ground.readbytes(result))
54+
55+
def test_copy_file_to_directory(self):
56+
if not isinstance(self.target_root, WritableLocalPath):
57+
self.skipTest('needs local target')
58+
source = self.source_root / 'fileA'
59+
target = self.target_root / 'copyA'
60+
self.target_ground.create_dir(target)
61+
self.assertRaises(OSError, source.copy, target)
62+
63+
def test_copy_file_to_itself(self):
64+
source = self.source_root / 'fileA'
65+
self.assertRaises(OSError, source.copy, source)
66+
self.assertRaises(OSError, source.copy, source, follow_symlinks=False)
67+
68+
def test_copy_dir(self):
69+
source = self.source_root / 'dirC'
70+
target = self.target_root / 'copyC'
71+
result = source.copy(target)
72+
self.assertEqual(result, target)
73+
self.assertTrue(self.target_ground.isdir(target))
74+
self.assertTrue(self.target_ground.isfile(target / 'fileC'))
75+
self.assertEqual(self.target_ground.readtext(target / 'fileC'), 'this is file C\n')
76+
self.assertTrue(self.target_ground.isdir(target / 'dirD'))
77+
self.assertTrue(self.target_ground.isfile(target / 'dirD' / 'fileD'))
78+
self.assertEqual(self.target_ground.readtext(target / 'dirD' / 'fileD'), 'this is file D\n')
79+
80+
def test_copy_dir_follow_symlinks_true(self):
81+
if not self.source_ground.can_symlink:
82+
self.skipTest('needs symlink support on source')
83+
source = self.source_root / 'dirC'
84+
target = self.target_root / 'copyC'
85+
self.source_ground.create_symlink(source / 'linkC', 'fileC')
86+
self.source_ground.create_symlink(source / 'linkD', 'dirD')
87+
result = source.copy(target)
88+
self.assertEqual(result, target)
89+
self.assertTrue(self.target_ground.isdir(target))
90+
self.assertFalse(self.target_ground.islink(target / 'linkC'))
91+
self.assertTrue(self.target_ground.isfile(target / 'linkC'))
92+
self.assertEqual(self.target_ground.readtext(target / 'linkC'), 'this is file C\n')
93+
self.assertFalse(self.target_ground.islink(target / 'linkD'))
94+
self.assertTrue(self.target_ground.isdir(target / 'linkD'))
95+
self.assertTrue(self.target_ground.isfile(target / 'linkD' / 'fileD'))
96+
self.assertEqual(self.target_ground.readtext(target / 'linkD' / 'fileD'), 'this is file D\n')
97+
98+
def test_copy_dir_follow_symlinks_false(self):
99+
if not self.source_ground.can_symlink:
100+
self.skipTest('needs symlink support on source')
101+
if not self.target_ground.can_symlink:
102+
self.skipTest('needs symlink support on target')
103+
source = self.source_root / 'dirC'
104+
target = self.target_root / 'copyC'
105+
self.source_ground.create_symlink(source / 'linkC', 'fileC')
106+
self.source_ground.create_symlink(source / 'linkD', 'dirD')
107+
result = source.copy(target, follow_symlinks=False)
108+
self.assertEqual(result, target)
109+
self.assertTrue(self.target_ground.isdir(target))
110+
self.assertTrue(self.target_ground.islink(target / 'linkC'))
111+
self.assertEqual(self.target_ground.readlink(target / 'linkC'), 'fileC')
112+
self.assertTrue(self.target_ground.islink(target / 'linkD'))
113+
self.assertEqual(self.target_ground.readlink(target / 'linkD'), 'dirD')
114+
115+
def test_copy_dir_to_existing_directory(self):
116+
if not isinstance(self.target_root, WritableLocalPath):
117+
self.skipTest('needs local target')
118+
source = self.source_root / 'dirC'
119+
target = self.target_root / 'copyC'
120+
self.target_ground.create_dir(target)
121+
self.assertRaises(FileExistsError, source.copy, target)
122+
123+
def test_copy_dir_to_itself(self):
124+
source = self.source_root / 'dirC'
125+
self.assertRaises(OSError, source.copy, source)
126+
self.assertRaises(OSError, source.copy, source, follow_symlinks=False)
127+
128+
def test_copy_dir_into_itself(self):
129+
source = self.source_root / 'dirC'
130+
target = self.source_root / 'dirC' / 'dirD' / 'copyC'
131+
self.assertRaises(OSError, source.copy, target)
132+
self.assertRaises(OSError, source.copy, target, follow_symlinks=False)
133+
134+
def test_copy_into(self):
135+
source = self.source_root / 'fileA'
136+
target_dir = self.target_root / 'dirA'
137+
self.target_ground.create_dir(target_dir)
138+
result = source.copy_into(target_dir)
139+
self.assertEqual(result, target_dir / 'fileA')
140+
self.assertTrue(self.target_ground.isfile(result))
141+
self.assertEqual(self.source_ground.readbytes(source),
142+
self.target_ground.readbytes(result))
143+
144+
def test_copy_into_empty_name(self):
145+
source = self.source_root.with_segments()
146+
target_dir = self.target_root / 'dirA'
147+
self.target_ground.create_dir(target_dir)
148+
self.assertRaises(ValueError, source.copy_into, target_dir)
149+
150+
151+
class ZipToZipPathCopyTest(CopyTestBase, unittest.TestCase):
152+
source_ground = ZipPathGround(ReadableZipPath)
153+
target_ground = ZipPathGround(WritableZipPath)
154+
155+
156+
class ZipToLocalPathCopyTest(CopyTestBase, unittest.TestCase):
157+
source_ground = ZipPathGround(ReadableZipPath)
158+
target_ground = LocalPathGround(Path)
159+
160+
161+
class LocalToZipPathCopyTest(CopyTestBase, unittest.TestCase):
162+
source_ground = LocalPathGround(Path)
163+
target_ground = ZipPathGround(WritableZipPath)
164+
165+
166+
class LocalToLocalPathCopyTest(CopyTestBase, unittest.TestCase):
167+
source_ground = LocalPathGround(Path)
168+
target_ground = LocalPathGround(Path)
169+
170+
171+
if __name__ == "__main__":
172+
unittest.main()

Lib/test/test_pathlib/test_pathlib_abc.py

Lines changed: 0 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -345,144 +345,6 @@ class RWPathTest(WritablePathTest, ReadablePathTest):
345345
cls = DummyRWPath
346346
can_symlink = False
347347

348-
def test_copy_file(self):
349-
base = self.cls(self.base)
350-
source = base / 'fileA'
351-
target = base / 'copyA'
352-
result = source.copy(target)
353-
self.assertEqual(result, target)
354-
self.assertTrue(result.info.exists())
355-
self.assertEqual(source.read_text(), result.read_text())
356-
357-
def test_copy_file_to_existing_file(self):
358-
base = self.cls(self.base)
359-
source = base / 'fileA'
360-
target = base / 'dirB' / 'fileB'
361-
result = source.copy(target)
362-
self.assertEqual(result, target)
363-
self.assertTrue(result.info.exists())
364-
self.assertEqual(source.read_text(), result.read_text())
365-
366-
def test_copy_file_to_existing_directory(self):
367-
base = self.cls(self.base)
368-
source = base / 'fileA'
369-
target = base / 'dirA'
370-
self.assertRaises(OSError, source.copy, target)
371-
372-
def test_copy_file_empty(self):
373-
base = self.cls(self.base)
374-
source = base / 'empty'
375-
target = base / 'copyA'
376-
source.write_bytes(b'')
377-
result = source.copy(target)
378-
self.assertEqual(result, target)
379-
self.assertTrue(result.info.exists())
380-
self.assertEqual(result.read_bytes(), b'')
381-
382-
def test_copy_file_to_itself(self):
383-
base = self.cls(self.base)
384-
source = base / 'empty'
385-
source.write_bytes(b'')
386-
self.assertRaises(OSError, source.copy, source)
387-
self.assertRaises(OSError, source.copy, source, follow_symlinks=False)
388-
389-
def test_copy_dir_simple(self):
390-
base = self.cls(self.base)
391-
source = base / 'dirC'
392-
target = base / 'copyC'
393-
result = source.copy(target)
394-
self.assertEqual(result, target)
395-
self.assertTrue(result.info.is_dir())
396-
self.assertTrue(result.joinpath('dirD').info.is_dir())
397-
self.assertTrue(result.joinpath('dirD', 'fileD').info.is_file())
398-
self.assertEqual(result.joinpath('dirD', 'fileD').read_text(),
399-
"this is file D\n")
400-
self.assertTrue(result.joinpath('fileC').info.is_file())
401-
self.assertTrue(result.joinpath('fileC').read_text(),
402-
"this is file C\n")
403-
404-
def test_copy_dir_complex(self, follow_symlinks=True):
405-
def ordered_walk(path):
406-
for dirpath, dirnames, filenames in path.walk(follow_symlinks=follow_symlinks):
407-
dirnames.sort()
408-
filenames.sort()
409-
yield dirpath, dirnames, filenames
410-
base = self.cls(self.base)
411-
source = base / 'dirC'
412-
413-
if self.can_symlink:
414-
# Add some symlinks
415-
source.joinpath('linkC').symlink_to('fileC')
416-
source.joinpath('linkD').symlink_to('dirD', target_is_directory=True)
417-
418-
# Perform the copy
419-
target = base / 'copyC'
420-
result = source.copy(target, follow_symlinks=follow_symlinks)
421-
self.assertEqual(result, target)
422-
423-
# Compare the source and target trees
424-
source_walk = ordered_walk(source)
425-
target_walk = ordered_walk(result)
426-
for source_item, target_item in zip(source_walk, target_walk, strict=True):
427-
self.assertEqual(source_item[0].parts[len(source.parts):],
428-
target_item[0].parts[len(target.parts):]) # dirpath
429-
self.assertEqual(source_item[1], target_item[1]) # dirnames
430-
self.assertEqual(source_item[2], target_item[2]) # filenames
431-
# Compare files and symlinks
432-
for filename in source_item[2]:
433-
source_file = source_item[0].joinpath(filename)
434-
target_file = target_item[0].joinpath(filename)
435-
if follow_symlinks or not source_file.info.is_symlink():
436-
# Regular file.
437-
self.assertEqual(source_file.read_bytes(), target_file.read_bytes())
438-
elif source_file.info.is_dir():
439-
# Symlink to directory.
440-
self.assertTrue(target_file.info.is_dir())
441-
self.assertEqual(source_file.readlink(), target_file.readlink())
442-
else:
443-
# Symlink to file.
444-
self.assertEqual(source_file.read_bytes(), target_file.read_bytes())
445-
self.assertEqual(source_file.readlink(), target_file.readlink())
446-
447-
def test_copy_dir_complex_follow_symlinks_false(self):
448-
self.test_copy_dir_complex(follow_symlinks=False)
449-
450-
def test_copy_dir_to_existing_directory(self):
451-
base = self.cls(self.base)
452-
source = base / 'dirC'
453-
target = base / 'copyC'
454-
target.mkdir()
455-
target.joinpath('dirD').mkdir()
456-
self.assertRaises(FileExistsError, source.copy, target)
457-
458-
def test_copy_dir_to_itself(self):
459-
base = self.cls(self.base)
460-
source = base / 'dirC'
461-
self.assertRaises(OSError, source.copy, source)
462-
self.assertRaises(OSError, source.copy, source, follow_symlinks=False)
463-
464-
def test_copy_dir_into_itself(self):
465-
base = self.cls(self.base)
466-
source = base / 'dirC'
467-
target = base / 'dirC' / 'dirD' / 'copyC'
468-
self.assertRaises(OSError, source.copy, target)
469-
self.assertRaises(OSError, source.copy, target, follow_symlinks=False)
470-
self.assertFalse(target.info.exists())
471-
472-
def test_copy_into(self):
473-
base = self.cls(self.base)
474-
source = base / 'fileA'
475-
target_dir = base / 'dirA'
476-
result = source.copy_into(target_dir)
477-
self.assertEqual(result, target_dir / 'fileA')
478-
self.assertTrue(result.info.exists())
479-
self.assertEqual(source.read_text(), result.read_text())
480-
481-
def test_copy_into_empty_name(self):
482-
source = self.cls('')
483-
target_dir = self.base
484-
self.assertRaises(ValueError, source.copy_into, target_dir)
485-
486348

487349
class ReadablePathWalkTest(unittest.TestCase):
488350
cls = DummyReadablePath

0 commit comments

Comments
 (0)