Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@
class Error(OSError):
pass

class ErrorGroup(Error):
"""Raised when multiple exceptions have been caught"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Raised when multiple exceptions have been caught"""
"""Raised when multiple exceptions have been caught."""


class SameFileError(Error):
"""Raised when source and destination are the same file."""

Expand Down Expand Up @@ -590,12 +593,9 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function,
copytree(srcobj, dstname, symlinks, ignore, copy_function,
ignore_dangling_symlinks, dirs_exist_ok)
else:
# Will raise a SpecialFileError for unsupported file types
copy_function(srcobj, dstname)
# catch the Error from the recursive copytree so that we can
# continue with other files
except Error as err:
errors.extend(err.args[0])
except ErrorGroup as err_group:
errors.extend(err_group.args[0])
except OSError as why:
errors.append((srcname, dstname, str(why)))
try:
Expand All @@ -605,7 +605,7 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function,
if getattr(why, 'winerror', None) is None:
errors.append((src, dst, str(why)))
if errors:
raise Error(errors)
raise ErrorGroup(errors)
return dst

def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
Expand Down
33 changes: 33 additions & 0 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,39 @@ def test_copytree_subdirectory(self):
rv = shutil.copytree(src_dir, dst_dir)
self.assertEqual(['pol'], os.listdir(rv))

def test_copytree_to_itself_gives_sensible_error_message(self):
base_dir = self.mkdtemp()
self.addCleanup(shutil.rmtree, base_dir, ignore_errors=True)
src_dir = os.path.join(base_dir, "src")
os.makedirs(src_dir)
create_file((src_dir, "somefilename"), "somecontent")
self._assert_are_the_same_file_is_raised(src_dir, src_dir)

@os_helper.skip_unless_symlink
def test_copytree_to_backpointing_symlink_gives_sensible_error_message(self):
base_dir = self.mkdtemp()
self.addCleanup(shutil.rmtree, base_dir, ignore_errors=True)
src_dir = os.path.join(base_dir, "src")
target_dir = os.path.join(base_dir, "target")
os.makedirs(src_dir)
os.makedirs(target_dir)
some_file = os.path.join(src_dir, "somefilename")
create_file(some_file, "somecontent")
os.symlink(some_file, os.path.join(target_dir, "somefilename"))
self._assert_are_the_same_file_is_raised(src_dir, target_dir)

def _assert_are_the_same_file_is_raised(self, src_dir, target_dir):
try:
shutil.copytree(src_dir, target_dir, dirs_exist_ok=True)
self.fail("shutil.Error should have been raised")
except Error as error:
Comment on lines +1124 to +1127
Copy link
Member

@picnixz picnixz Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use self.assertRaisesRegex instead of try-except please or work with the context manager.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think self.assertRaisesRegex is appropriate here, since we're checking a tuple-valued args.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right. Then just assertRaises

self.assertEqual(len(error.args[0]), 1)
if sys.platform == "win32":
self.assertIn("it is being used by another process", error.args[0][0][2])
else:
self.assertIn("are the same file", error.args[0][0][2])


class TestCopy(BaseTest, unittest.TestCase):

### shutil.copymode
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Make exception from :func:`shutil.copytree` readable when a
:exc:`shutil.SameFileError` is raised.
Loading