Skip to content

Fix os.ftruncate support #1331

Description

@davidlbaird

Describe the bug

FakeOsModule.ftruncate calls get_object() on the FakeFileWrappe returned by get_open_file(fd), returning a FakeFile, and then checking isinstance(file_object, FakeFileWrapper). This check is always evaluated to False, causing os.ftruncate to unconditionally raise OSError(errno.EBADF) for any open file descriptor.

Not a complex fix, in fake_os.py

This change fixes the type check to inspect the FakeFileWrapper returned by get_open_file(fd) before accessing the underlying FakeFile object.

def ftruncate(self, fd: int, length: int) -> None:
        """Truncate the file corresponding to fd, so that it is
         length bytes in size. If length is larger than the current size,
         the file is filled up with zero bytes.

        Args:
            fd: (int) File descriptor for the file object.
            length: (int) Maximum length of the file after truncating it.

        Raises:
            OSError: if the file descriptor is invalid
        """
        file_handle = self.filesystem.get_open_file(fd)
        if isinstance(file_handle, FakeFileWrapper):
            file_handle.get_object().size = length
        else:
            self.filesystem.raise_os_error(errno.EBADF)

How To Reproduce

Modify the function test_ftruncate in tests/fake_os_test.py, and change the line self.os.truncate(fd, 10) to self.os.ftruncate(fd, 10), which is the os function that should be tested by the function.

   def test_ftruncate(self):
        if self.is_pypy:
            # not correctly supported
            self.skip_real_fs()
        self.assert_raises_os_error(errno.EBADF, self.os.ftruncate, 50, 10)
        file_path = self.make_path("some_file")
        self.create_file(file_path, contents="0123456789012345")

        fd = self.os.open(file_path, os.O_RDWR)
        self.os.ftruncate(fd, 10)
        self.assertEqual(10, self.os.stat(file_path).st_size)
        with self.open(file_path, encoding="utf8") as f:
            self.assertEqual("0123456789", f.read())

Failure

=================================== FAILURES ===================================
____________________ FakeOsModuleTest.test_truncate_with_fd ____________________

self = <pyfakefs.tests.fake_os_test.FakeOsModuleTest testMethod=test_truncate_with_fd>

    def test_truncate_with_fd(self):
        if os.truncate not in os.supports_fd:
            self.skip_real_fs()
        self.assert_raises_os_error(errno.EBADF, self.os.ftruncate, 50, 10)
        file_path = self.make_path("some_file")
        self.create_file(file_path, contents="01234567890123456789")
    
        fd = self.os.open(file_path, os.O_RDWR)
>       self.os.ftruncate(fd, 10)

tests/fake_os_test.py:2991: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
fake_os.py:1490: in wrapped
    return f(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^
fake_os.py:1027: in ftruncate
    self.filesystem.raise_os_error(errno.EBADF)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <pyfakefs.fake_filesystem.FakeFilesystem object at 0x53bf3ddea350>
err_no = 9, filename = None, winerror = None

    def raise_os_error(
        self,
        err_no: int,
        filename: AnyString | None = None,
        winerror: int | None = None,
    ) -> NoReturn:
        """Raises OSError.
        The error message is constructed from the given error code and shall
        start with the error string issued in the real system.
        Note: this is not true under Windows if winerror is given - in this
        case a localized message specific to winerror will be shown in the
        real file system.
    
        Args:
            err_no: A numeric error code from the C variable errno.
            filename: The name of the affected file, if any.
            winerror: Windows only - the specific Windows error code.
        """
        message = os.strerror(err_no)
        if winerror is not None and sys.platform == "win32" and self.is_windows_fs:
            raise OSError(err_no, message, filename, winerror)
>       raise OSError(err_no, message, filename)
E       OSError: [Errno 9] Bad file descriptor

fake_filesystem.py:526: OSError

Your environment

Linux-7.1.6-1rodete1-amd64-x86_64-with-glibc2.42
Python 3.13.14 (main, Jun 10 2026, 18:10:12) [GCC 15.2.0]

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions