Skip to content

Commit

Permalink
Set the os.supports_xxx properties for the fake filesystem
Browse files Browse the repository at this point in the history
- return the faked functions instead of the real ones
- fixes pytest-dev#799
  • Loading branch information
mrbean-bremen committed Apr 2, 2023
1 parent c819a40 commit 6a8b0fa
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 27 deletions.
7 changes: 7 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# pyfakefs Release Notes
The released versions correspond to PyPI releases.

## Unreleased

### Fixes
* Properties defining the capabilities of some `os` functions like
`os.supports_follow_symlinks` are now properly faked to contain the fake functions
if the real functions are faked (see [#799](../../issues/799))

## [Version 5.2.0](https://pypi.python.org/pypi/pyfakefs/5.2.0) (2023-03-31)
Supports current Python 3.12 version (alpha 6). We plan to make patch releases in
case of breaking changes in alpha or beta versions.
Expand Down
47 changes: 44 additions & 3 deletions pyfakefs/fake_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
cast,
AnyStr,
TYPE_CHECKING,
Set,
)

from pyfakefs.extra_packages import use_scandir
Expand Down Expand Up @@ -146,6 +147,10 @@ def __init__(self, filesystem: "FakeFilesystem"):
self.filesystem = filesystem
self.os_module: Any = os
self.path = FakePathModule(self.filesystem, self)
self._supports_follow_symlinks: Optional[Set] = None
self._supports_dir_fd: Optional[Set] = None
self._supports_effective_ids: Optional[Set] = None
self._supports_fd: Optional[Set] = None

@property
def devnull(self) -> str:
Expand Down Expand Up @@ -890,8 +895,7 @@ def _path_with_dir_fd(
path = path
if dir_fd is not None:
# check if fd is supported for the built-in real function
real_fct = getattr(os, fct.__name__)
if real_fct not in self.supports_dir_fd:
if fct not in self.supports_dir_fd:
raise NotImplementedError("dir_fd unavailable on this platform")
if isinstance(path, int):
raise ValueError(
Expand Down Expand Up @@ -998,7 +1002,7 @@ def chmod(
the link itself is queried instead of the linked object.
"""
if not follow_symlinks and (
os.chmod not in os.supports_follow_symlinks or IS_PYPY
self.chmod not in self.supports_follow_symlinks or IS_PYPY
):
raise NotImplementedError(
"`follow_symlinks` for chmod() is not available " "on this system"
Expand Down Expand Up @@ -1268,6 +1272,43 @@ def sendfile(self, fd_out: int, fd_in: int, offset: int, count: int) -> int:
return written
return 0

def fake_functions(self, original_functions) -> Set:
functions = set()
for fn in original_functions:
if hasattr(self, fn.__name__):
functions.add(getattr(self, fn.__name__))
else:
functions.add(fn)
return functions

@property
def supports_follow_symlinks(self) -> Set[Callable]:
if self._supports_follow_symlinks is None:
self._supports_follow_symlinks = self.fake_functions(
self.os_module.supports_follow_symlinks
)
return self._supports_follow_symlinks

@property
def supports_dir_fd(self) -> Set[Callable]:
if self._supports_dir_fd is None:
self._supports_dir_fd = self.fake_functions(self.os_module.supports_dir_fd)
return self._supports_dir_fd

@property
def supports_fd(self) -> Set[Callable]:
if self._supports_fd is None:
self._supports_fd = self.fake_functions(self.os_module.supports_fd)
return self._supports_fd

@property
def supports_effective_ids(self) -> Set[Callable]:
if self._supports_effective_ids is None:
self._supports_effective_ids = self.fake_functions(
self.os_module.supports_effective_ids
)
return self._supports_effective_ids

def __getattr__(self, name: str) -> Any:
"""Forwards any unfaked calls to the standard os module."""
return getattr(self.os_module, name)
Expand Down
2 changes: 1 addition & 1 deletion pyfakefs/fake_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def chmod(self, pathobj, *args, **kwargs):

if (
not kwargs["follow_symlinks"]
and os.os_module.chmod not in os.supports_follow_symlinks
and os.os_module.chmod not in os.os_module.supports_follow_symlinks
):
raise NotImplementedError(
"`follow_symlinks` for chmod() is not available " "on this system"
Expand Down
11 changes: 11 additions & 0 deletions pyfakefs/tests/fake_filesystem_shutil_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,17 @@ def test_copystat(self):
self.assertAlmostEqual(src_stat.st_atime, dst_stat.st_atime, places=2)
self.assertAlmostEqual(src_stat.st_mtime, dst_stat.st_mtime, places=2)

def test_copystat_symlinks(self):
"""Regression test for #799"""
self.skip_if_symlink_not_supported()
f = self.make_path("xyzzy")
self.create_file(f)
sym1 = self.make_path("sym1")
sym2 = self.make_path("sym2")
self.create_symlink(sym1, f)
self.create_symlink(sym2, f)
shutil.copystat(sym1, sym2, follow_symlinks=False)

def test_copy2(self):
src_file = self.make_path("xyzzy")
self.create_file(src_file)
Expand Down
59 changes: 37 additions & 22 deletions pyfakefs/tests/fake_os_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2011,7 +2011,7 @@ def test_chmod_no_follow_symlink(self):
self.createTestFile(path)
link_path = self.make_path("link_to_some_file")
self.create_symlink(link_path, path)
if os.chmod not in os.supports_follow_symlinks or IS_PYPY:
if self.os.chmod not in self.os.supports_follow_symlinks or IS_PYPY:
with self.assertRaises(NotImplementedError):
self.os.chmod(link_path, 0o6543, follow_symlinks=False)
else:
Expand Down Expand Up @@ -2866,6 +2866,21 @@ def test_ftruncate(self):
with self.open(file_path) as f:
self.assertEqual("0123456789", f.read())

def test_capabilities(self):
"""Make sure that the fake capabilities are the same as the real ones."""
self.assertEqual(
self.os.stat in self.os.supports_follow_symlinks,
os.stat in os.supports_follow_symlinks,
)
self.assertEqual(self.os.stat in self.os.supports_fd, os.stat in os.supports_fd)
self.assertEqual(
self.os.stat in self.os.supports_dir_fd, os.stat in os.supports_dir_fd
)
self.assertEqual(
self.os.stat in self.os.supports_effective_ids,
os.stat in os.supports_effective_ids,
)


class RealOsModuleTest(FakeOsModuleTest):
def use_real_fs(self):
Expand Down Expand Up @@ -4661,7 +4676,7 @@ def use_real_fs(self):
class FakeOsModuleDirFdTest(FakeOsModuleTestBase):
def setUp(self):
super(FakeOsModuleDirFdTest, self).setUp()
self.os.supports_dir_fd = set()
self.os.supports_dir_fd.clear()
self.filesystem.is_windows_fs = False
self.filesystem.create_dir("/foo/bar")
self.dir_fd = self.os.open("/foo", os.O_RDONLY)
Expand All @@ -4675,7 +4690,7 @@ def test_access(self):
self.os.F_OK,
dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.access)
self.os.supports_dir_fd.add(self.os.access)
self.assertTrue(self.os.access("baz", self.os.F_OK, dir_fd=self.dir_fd))

def test_chmod(self):
Expand All @@ -4686,7 +4701,7 @@ def test_chmod(self):
0o6543,
dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.chmod)
self.os.supports_dir_fd.add(self.os.chmod)
self.os.chmod("baz", 0o6543, dir_fd=self.dir_fd)
st = self.os.stat("/foo/baz")
self.assert_mode_equal(0o6543, st.st_mode)
Expand All @@ -4701,7 +4716,7 @@ def test_chown(self):
101,
dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.chown)
self.os.supports_dir_fd.add(self.os.chown)
self.os.chown("baz", 100, 101, dir_fd=self.dir_fd)
st = self.os.stat("/foo/baz")
self.assertEqual(st[stat.ST_UID], 100)
Expand All @@ -4715,7 +4730,7 @@ def test_link_src_fd(self):
"/bat",
src_dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.link)
self.os.supports_dir_fd.add(self.os.link)
self.os.link("baz", "/bat", src_dir_fd=self.dir_fd)
self.assertTrue(self.os.path.exists("/bat"))

Expand All @@ -4727,7 +4742,7 @@ def test_link_dst_fd(self):
"/bat",
dst_dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.link)
self.os.supports_dir_fd.add(self.os.link)
self.os.link("/foo/baz", "bat", dst_dir_fd=self.dir_fd)
self.assertTrue(self.os.path.exists("/foo/bat"))

Expand All @@ -4739,7 +4754,7 @@ def test_symlink(self):
"/bat",
dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.symlink)
self.os.supports_dir_fd.add(self.os.symlink)
self.os.symlink("baz", "/bat", dir_fd=self.dir_fd)
self.assertTrue(self.os.path.exists("/bat"))

Expand All @@ -4753,35 +4768,35 @@ def test_readlink(self):
"/geo/metro/lemon/pie",
dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.readlink)
self.os.supports_dir_fd.add(self.os.readlink)
self.assertEqual(
"/foo/baz",
self.os.readlink("/geo/metro/lemon/pie", dir_fd=self.dir_fd),
)

def test_stat(self):
self.assertRaises(NotImplementedError, self.os.stat, "baz", dir_fd=self.dir_fd)
self.os.supports_dir_fd.add(os.stat)
self.os.supports_dir_fd.add(self.os.stat)
st = self.os.stat("baz", dir_fd=self.dir_fd)
self.assertEqual(st.st_mode, 0o100666)

def test_lstat(self):
self.assertRaises(NotImplementedError, self.os.lstat, "baz", dir_fd=self.dir_fd)
self.os.supports_dir_fd.add(os.lstat)
self.os.supports_dir_fd.add(self.os.lstat)
st = self.os.lstat("baz", dir_fd=self.dir_fd)
self.assertEqual(st.st_mode, 0o100666)

def test_mkdir(self):
self.assertRaises(
NotImplementedError, self.os.mkdir, "newdir", dir_fd=self.dir_fd
)
self.os.supports_dir_fd.add(os.mkdir)
self.os.supports_dir_fd.add(self.os.mkdir)
self.os.mkdir("newdir", dir_fd=self.dir_fd)
self.assertTrue(self.os.path.exists("/foo/newdir"))

def test_rmdir(self):
self.assertRaises(NotImplementedError, self.os.rmdir, "bar", dir_fd=self.dir_fd)
self.os.supports_dir_fd.add(os.rmdir)
self.os.supports_dir_fd.add(self.os.rmdir)
self.os.rmdir("bar", dir_fd=self.dir_fd)
self.assertFalse(self.os.path.exists("/foo/bar"))

Expand All @@ -4790,7 +4805,7 @@ def test_mknod(self):
self.assertRaises(
NotImplementedError, self.os.mknod, "newdir", dir_fd=self.dir_fd
)
self.os.supports_dir_fd.add(os.mknod)
self.os.supports_dir_fd.add(self.os.mknod)
self.os.mknod("newdir", dir_fd=self.dir_fd)
self.assertTrue(self.os.path.exists("/foo/newdir"))

Expand All @@ -4802,7 +4817,7 @@ def test_rename_src_fd(self):
"/foo/batz",
src_dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.rename)
self.os.supports_dir_fd.add(self.os.rename)
self.os.rename("bar", "/foo/batz", src_dir_fd=self.dir_fd)
self.assertTrue(self.os.path.exists("/foo/batz"))

Expand All @@ -4814,7 +4829,7 @@ def test_rename_dst_fd(self):
"/foo/batz",
dst_dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.rename)
self.os.supports_dir_fd.add(self.os.rename)
self.os.rename("/foo/bar", "batz", dst_dir_fd=self.dir_fd)
self.assertTrue(self.os.path.exists("/foo/batz"))

Expand All @@ -4826,7 +4841,7 @@ def test_replace_src_fd(self):
"/foo/batz",
src_dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.rename)
self.os.supports_dir_fd.add(self.os.rename)
self.os.replace("bar", "/foo/batz", src_dir_fd=self.dir_fd)
self.assertTrue(self.os.path.exists("/foo/batz"))

Expand All @@ -4838,23 +4853,23 @@ def test_replace_dst_fd(self):
"/foo/batz",
dst_dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.rename)
self.os.supports_dir_fd.add(self.os.rename)
self.os.replace("/foo/bar", "batz", dst_dir_fd=self.dir_fd)
self.assertTrue(self.os.path.exists("/foo/batz"))

def test_remove(self):
self.assertRaises(
NotImplementedError, self.os.remove, "baz", dir_fd=self.dir_fd
)
self.os.supports_dir_fd.add(os.remove)
self.os.supports_dir_fd.add(self.os.remove)
self.os.remove("baz", dir_fd=self.dir_fd)
self.assertFalse(self.os.path.exists("/foo/baz"))

def test_unlink(self):
self.assertRaises(
NotImplementedError, self.os.unlink, "baz", dir_fd=self.dir_fd
)
self.os.supports_dir_fd.add(os.unlink)
self.os.supports_dir_fd.add(self.os.unlink)
self.os.unlink("baz", dir_fd=self.dir_fd)
self.assertFalse(self.os.path.exists("/foo/baz"))

Expand All @@ -4866,7 +4881,7 @@ def test_utime(self):
(1, 2),
dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.utime)
self.os.supports_dir_fd.add(self.os.utime)
self.os.utime("baz", times=(1, 2), dir_fd=self.dir_fd)
st = self.os.stat("/foo/baz")
self.assertEqual(1, st.st_atime)
Expand All @@ -4880,7 +4895,7 @@ def test_open(self):
os.O_RDONLY,
dir_fd=self.dir_fd,
)
self.os.supports_dir_fd.add(os.open)
self.os.supports_dir_fd.add(self.os.open)
fd = self.os.open("baz", os.O_RDONLY, dir_fd=self.dir_fd)
self.assertLess(0, fd)

Expand Down
2 changes: 1 addition & 1 deletion pyfakefs/tests/fake_pathlib_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ def test_chmod_no_followsymlinks(self):
self.skip_if_symlink_not_supported()
file_stat = self.os.stat(self.file_path)
link_stat = self.os.lstat(self.file_link_path)
if self.real_os.chmod not in os.supports_follow_symlinks or IS_PYPY:
if self.os.chmod not in self.os.supports_follow_symlinks or IS_PYPY:
with self.assertRaises(NotImplementedError):
self.path(self.file_link_path).chmod(0o444, follow_symlinks=False)
else:
Expand Down

0 comments on commit 6a8b0fa

Please sign in to comment.