Skip to content
Merged
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: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
default_language_version:
python: "3.10"
python: "python3"

repos:
- repo: "https://github.com/asottile/pyupgrade"
Expand Down
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ The released versions correspond to PyPI releases.

### Fixes

* fixed `os.path.sameopenfile` using real file descriptors instead of fake ones
(see [#1346](https://github.com/pytest-dev/pyfakefs/issues/1346))

* fixed a crash if the stack limit was set to a low value
(see [#1313](https://github.com/pytest-dev/pyfakefs/issues/1313))
* fixed `os.ftruncate` to inspect the `FakeFileWrapper` returned by
Expand Down
6 changes: 1 addition & 5 deletions pyfakefs/fake_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -1296,11 +1296,7 @@ def link(
OSError: if something already exists at new_path.
OSError: if the parent directory doesn't exist.
"""
if (
IS_PYPY
and not (self.filesystem.is_windows_fs and sys.version_info >= (3, 11))
and follow_symlinks is not None
):
if IS_PYPY and sys.version_info < (3, 11) and follow_symlinks is not None:
raise OSError(errno.EINVAL, "Invalid argument: follow_symlinks")
if follow_symlinks is None:
follow_symlinks = True
Expand Down
13 changes: 13 additions & 0 deletions pyfakefs/fake_path.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ def dir() -> list[str]:
"split",
"splitdrive",
"samefile",
"sameopenfile",
]
if sys.version_info >= (3, 12):
dir_list += ["isjunction", "splitroot"]
Expand Down Expand Up @@ -389,6 +390,18 @@ def samefile(self, path1: AnyStr, path2: AnyStr) -> bool:
stat2 = self.filesystem.stat(path2)
return stat1.st_ino == stat2.st_ino and stat1.st_dev == stat2.st_dev

def sameopenfile(self, fd1: int, fd2: int) -> bool:
"""Return whether two file descriptors refer to the same file.

Args:
fd1: First open file descriptor.
fd2: Second open file descriptor.

Raises:
OSError: If either file descriptor is invalid.
"""
return self._os_path.samestat(self.os.fstat(fd1), self.os.fstat(fd2))

@overload
def _join_real_path(
self, path: str, rest: str, seen: dict[str, str | None]
Expand Down
30 changes: 30 additions & 0 deletions pyfakefs/tests/fake_filesystem_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,36 @@ def test_samefile(self):
self.assertTrue(self.path.samefile(file_path1, "!foo!..!foo!bar!..!bar!baz"))
self.assertTrue(self.path.samefile(file_path1, b"!foo!..!foo!bar!..!bar!baz"))

def test_sameopenfile(self):
def check_sameopenfile(path_module, open_file, file_path1, file_path2):
with open_file(file_path1) as first, open_file(file_path1) as second:

@mrbean-bremen mrbean-bremen Oct 11, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I just realized that this test class does not test both fake and real fs - this is a change I made in a branch which is not yet merged. Anyway, this is the correct place for the test.

first_fd, second_fd = first.fileno(), second.fileno()
self.assertTrue(path_module.sameopenfile(first_fd, second_fd))
self.assertTrue(path_module.sameopenfile(first_fd, first_fd))
with open_file(file_path2) as other:
self.assertFalse(path_module.sameopenfile(first_fd, other.fileno()))

with self.assertRaises(OSError) as error:
path_module.sameopenfile(first_fd, second_fd)
self.assertEqual(error.exception.errno, errno.EBADF)

file_path1 = "!foo!bar"
file_path2 = "!foo!baz"
fake_file_open = fake_open.FakeFileOpen(self.filesystem)
for os_type in OSType:
with self.subTest(os_type=os_type):
self.filesystem.os = os_type
self.filesystem.create_file(file_path1)
self.filesystem.create_file(file_path2)
check_sameopenfile(self.path, fake_file_open, file_path1, file_path2)

with tempfile.TemporaryDirectory() as real_dir:
real_file1 = os.path.join(real_dir, "bar")
real_file2 = os.path.join(real_dir, "baz")
with open(real_file1, "w"), open(real_file2, "w"):
pass
check_sameopenfile(os.path, open, real_file1, real_file2)

def test_exists(self):
file_path = "foo!bar!baz"
file_path_bytes = b"foo!bar!baz"
Expand Down
11 changes: 8 additions & 3 deletions pyfakefs/tests/fake_os_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2772,7 +2772,10 @@ def test_link_count1(self):
self.os.unlink(file1_path)
self.assertEqual(self.os.stat(file2_path).st_nlink, 1)

@unittest.skipIf(IS_PYPY, "follow_symlinks not supported in PyPi")
@unittest.skipIf(
IS_PYPY and sys.version_info < (3, 11),
"follow_symlinks not supported in PyPy before Python 3.11",
)
def test_link_no_follow_symlink(self):
skip_if_symlink_not_supported()
target_path = self.make_path("target_path")
Expand All @@ -2783,8 +2786,10 @@ def test_link_no_follow_symlink(self):
self.os.link(symlink_path, link_path, follow_symlinks=False)
self.assertTrue(self.os.path.islink(link_path))

@unittest.skipIf(not IS_PYPY, "follow_symlinks only not supported in PyPi")
@unittest.skipIf(IS_WIN, "supported in PyPy 3.11 under Windows")
@unittest.skipIf(
not IS_PYPY or IS_WIN or sys.version_info >= (3, 11),
"follow_symlinks only unsupported in PyPy before Python 3.11 on POSIX",
)
def test_link_follow_symlink_not_supported_inPypy(self):
skip_if_symlink_not_supported()
target_path = self.make_path("target_path")
Expand Down
Loading