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
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ The released versions correspond to PyPI releases.
* fixed `os.rename` failing for the root user in a directory owned by
another user
(see [#1341](https://github.com/pytest-dev/pyfakefs/issues/1341))
* fixed `os.rename` and `os.replace` succeeding under Posix when renaming
a file to a path with a trailing separator, or from `file/`

## [Version 6.2.0](https://pypi.python.org/pypi/pyfakefs/6.2.0) (2026-04-12)

Expand Down
26 changes: 26 additions & 0 deletions pyfakefs/fake_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -2005,6 +2005,7 @@ def rename(
old_path = make_string_path(old_file_path)
new_path = make_string_path(new_file_path)
ends_with_sep = self.ends_with_path_separator(old_path)
new_ends_with_sep = self.ends_with_path_separator(new_path)
old_path = self.absnormpath(old_path)
new_path = self.absnormpath(new_path)
if not self.exists(old_path, check_link=True):
Expand All @@ -2013,6 +2014,10 @@ def rename(
self._handle_broken_link_with_trailing_sep(old_path)

old_object = self.lresolve(old_path)
if not self.is_windows_fs and not S_ISDIR(old_object.st_mode):
self._handle_posix_non_dir_with_trailing_sep(
old_path, new_path, old_object, ends_with_sep, new_ends_with_sep
)
if not self.is_windows_fs:
self._handle_posix_dir_link_errors(new_path, old_path, ends_with_sep)

Expand Down Expand Up @@ -2066,6 +2071,27 @@ def _do_rename(self, old_dir_object, old_name, new_dir_object, new_name):
old_dir_object.add_entry(object_to_rename)
raise

def _handle_posix_non_dir_with_trailing_sep(
self,
old_path: AnyStr,
new_path: AnyStr,
old_object: AnyFile,
old_ends_with_sep: bool,
new_ends_with_sep: bool,
) -> None:
# a trailing separator requires a directory, so renaming a file
# (or a symlink) from or to such a path fails
if old_ends_with_sep and S_ISREG(old_object.st_mode):
self.raise_os_error(errno.ENOTDIR, old_path)
if new_ends_with_sep and self.exists(self.splitpath(new_path)[0]):
error = errno.ENOTDIR
if self.is_macos:
if not self.exists(new_path):
error = errno.ENOENT
elif self.isdir(new_path):
error = errno.EISDIR
self.raise_os_error(error, new_path)

def _handle_broken_link_with_trailing_sep(self, path: AnyStr) -> None:
# note that the check for trailing sep has to be done earlier
if self.islink(path) and not self.exists(path):
Expand Down
35 changes: 35 additions & 0 deletions pyfakefs/tests/fake_os_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2608,6 +2608,41 @@ def test_rename_to_path_ending_with_sep(self):
errno.ENOTDIR, self.os.rename, file_path + self.os.sep, file_path
)

def check_rename_file_to_path_ending_with_sep(self, errors):
file_path = self.make_path("foo")
self.create_file(file_path)
self.create_dir(self.make_path("dir"))
for name, error in zip(("bar", "foo", "dir"), errors):
new_path = self.make_path(name) + self.os.sep
self.assert_raises_os_error(error, self.os.rename, file_path, new_path)
self.assert_raises_os_error(error, self.os.replace, file_path, new_path)
self.assertTrue(self.os.path.isfile(file_path))
self.assertFalse(self.os.path.exists(self.make_path("bar")))

def test_rename_file_to_path_ending_with_sep_linux(self):
self.check_linux_only()
self.check_rename_file_to_path_ending_with_sep(
(errno.ENOTDIR, errno.ENOTDIR, errno.ENOTDIR)
)

def test_rename_file_to_path_ending_with_sep_macos(self):
self.check_macos_only()
self.check_rename_file_to_path_ending_with_sep(
(errno.ENOENT, errno.ENOTDIR, errno.EISDIR)
)

def test_rename_file_with_trailing_sep_posix(self):
self.check_posix_only()
file_path = self.make_path("foo")
self.create_file(file_path)
self.assert_raises_os_error(
errno.ENOTDIR,
self.os.rename,
file_path + self.os.sep,
self.make_path("bar"),
)
self.assertTrue(self.os.path.isfile(file_path))

def test_rmdir_link_with_trailing_sep_linux(self):
self.check_linux_only()
dir_path = self.make_path("foo")
Expand Down