Repository: pytest-dev/pyfakefs
Pull request #989 (2024-03-17 09:33:48)

Fixes for the last commits (os.dup / O_DIRECTORY)
#### Tasks
- [x] Unit tests added that reproduce the issue or prove feature is working
- [x] Fix or feature added
- [ ] Entry to release notes added - not needed, belongs to existing entries
- [x] Pre-commit CI shows no errors
- [x] Unit tests passing



Patch:
diff --git a/pyfakefs/fake_file.py b/pyfakefs/fake_file.py
index b91127f..9d46d77 100644
--- a/pyfakefs/fake_file.py
+++ b/pyfakefs/fake_file.py
@@ -845,6 +845,11 @@ class FakeFileWrapper:
 
     def close(self) -> None:
         """Close the file."""
+        self.close_fd(self.filedes)
+
+    def close_fd(self, fd: Optional[int]) -> None:
+        """Close the file for the given file descriptor."""
+
         # ignore closing a closed file
         if not self._is_open():
             return
@@ -864,11 +869,11 @@ class FakeFileWrapper:
             if self._filesystem.is_windows_fs and self._changed:
                 self.file_object.st_mtime = helpers.now()
 
-        assert self.filedes is not None
+        assert fd is not None
         if self._closefd:
-            self._filesystem.close_open_file(self.filedes)
+            self._filesystem.close_open_file(fd)
         else:
-            open_files = self._filesystem.open_files[self.filedes]
+            open_files = self._filesystem.open_files[fd]
             assert open_files is not None
             open_files.remove(self)
         if self.delete_on_close:
@@ -1256,6 +1261,9 @@ class StandardStreamWrapper:
     def close(self) -> None:
         """We do not support closing standard streams."""
 
+    def close_fd(self, fd: Optional[int]) -> None:
+        """We do not support closing standard streams."""
+
     def is_stream(self) -> bool:
         return True
 
@@ -1287,8 +1295,12 @@ class FakeDirWrapper:
 
     def close(self) -> None:
         """Close the directory."""
-        assert self.filedes is not None
-        self._filesystem.close_open_file(self.filedes)
+        self.close_fd(self.filedes)
+
+    def close_fd(self, fd: Optional[int]) -> None:
+        """Close the directory."""
+        assert fd is not None
+        self._filesystem.close_open_file(fd)
 
 
 class FakePipeWrapper:
@@ -1351,8 +1363,12 @@ class FakePipeWrapper:
 
     def close(self) -> None:
         """Close the pipe descriptor."""
-        assert self.filedes is not None
-        open_files = self._filesystem.open_files[self.filedes]
+        self.close_fd(self.filedes)
+
+    def close_fd(self, fd: Optional[int]) -> None:
+        """Close the pipe descriptor with the given file descriptor."""
+        assert fd is not None
+        open_files = self._filesystem.open_files[fd]
         assert open_files is not None
         open_files.remove(self)
         if self.real_file:
diff --git a/pyfakefs/fake_os.py b/pyfakefs/fake_os.py
index 565fb8a..26be463 100644
--- a/pyfakefs/fake_os.py
+++ b/pyfakefs/fake_os.py
@@ -254,7 +254,11 @@ class FakeOsModule:
         has_directory_flag = (
             hasattr(os, "O_DIRECTORY") and flags & os.O_DIRECTORY == os.O_DIRECTORY
         )
-        if has_directory_flag and not self.filesystem.isdir(path):
+        if (
+            has_directory_flag
+            and self.filesystem.exists(path)
+            and not self.filesystem.isdir(path)
+        ):
             raise OSError(errno.ENOTDIR, "path is not a directory", path)
 
         has_follow_flag = (
@@ -322,7 +326,7 @@ class FakeOsModule:
             TypeError: if file descriptor is not an integer.
         """
         file_handle = self.filesystem.get_open_file(fd)
-        file_handle.close()
+        file_handle.close_fd(fd)
 
     def dup(self, fd: int) -> int:
         file_handle = self.filesystem.get_open_file(fd)


Test patch:
diff --git a/pyfakefs/tests/fake_os_test.py b/pyfakefs/tests/fake_os_test.py
index c9f69c1..dc877dc 100644
--- a/pyfakefs/tests/fake_os_test.py
+++ b/pyfakefs/tests/fake_os_test.py
@@ -605,8 +605,10 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
         self.check_open_raises_with_trailing_separator(errno.EINVAL)
 
     @unittest.skipIf(not hasattr(os, "O_DIRECTORY"), "opening directory not supported")
-    def test_open_raises_if_not_dir(self):
+    def test_open_with_o_directory(self):
         self.check_posix_only()
+        with self.assertRaises(FileNotFoundError):
+            self.os.open("bogus", os.O_RDONLY | os.O_DIRECTORY)
         file_path = self.make_path("file.txt")
         self.create_file(file_path, contents="foo")
         with self.assertRaises(NotADirectoryError):
@@ -3012,8 +3014,8 @@ class FakeOsModuleTest(FakeOsModuleTestBase):
         fd2 = self.os.dup(fd1)
         self.assertEqual(b"hey", self.os.read(fd1, 3))
         self.assertEqual(b"there", self.os.read(fd1, 10))
-        self.os.close(fd1)
         self.os.close(fd2)
+        self.os.close(fd1)
 
     def test_dup_uses_freed_fd(self):
         file_path1 = self.make_path("foo.txt")