Skip to content

Negative truncate lengths raise ENOSPC / ValueError instead of OSError(EINVAL) like the real OS #1372

Open

Description

@rahul05ranjan

Summary

Negative truncate lengths raise the wrong error under the fake filesystem: the real OS rejects them with OSError(EINVAL), but the fake raises OSError(ENOSPC) on the os.truncate/os.ftruncate paths and a plain ValueError on the file-object path. Code that handles invalid truncates by catching OSError (or by checking errno.EINVAL) behaves differently between the real and fake filesystem.

Steps to reproduce

Real filesystem (CPython on Linux):

import os, tempfile
d = tempfile.mkdtemp()
p = os.path.join(d, "f")
open(p, "w").write("hello")

os.truncate(p, -1)     # OSError: [Errno 22] Invalid argument: '...f'
fd = os.open(p, os.O_WRONLY)
os.ftruncate(fd, -1)   # OSError: [Errno 22] Invalid argument
open(p, "r+").truncate(-1)  # OSError: [Errno 22] Invalid argument

Same code under pyfakefs (current main):

os.truncate(p, -1)      # OSError: [Errno 28] No space left on device: 'f'
os.ftruncate(fd, -1)    # OSError: [Errno 28] No space left on device
f.truncate(-1)          # ValueError: negative size value -1   (not even an OSError)

Root cause

  1. FakeFile._check_positive_int reuses ENOSPC for the negative/non-int case, and FakeOsModule.truncate/ftruncate funnel negative lengths into the size setter.
  2. FakeFileWrapper._truncate_wrapper delegates to the underlying StringIO/BytesIO, which raises ValueError("negative size value -1") — correct for real in-memory streams, but not for a file opened on the (fake) filesystem, where CPython raises OSError(EINVAL).

Real in-memory streams must keep the ValueError (real StringIO.truncate(-1) raises it too), so the fix belongs at the file end of the fake layer, not in the shared StringIO wrapper.

Expected behavior

For a file on the fake filesystem, all three forms should raise OSError with errno.EINVAL and the same message shape as the real OS. I'll send a PR.

Activity

  1. rahul05ranjan commented on Nov 9, 2026

    @rahul05ranjan
    ContributorAuthor

    Update while looking into this: #1343 (open since September) already covers the os.truncate() and os.ftruncate() parts with the same EINVAL plus permission checks — credit to @kratos0718 for getting there first. I've re-scoped the PR I mentioned to the piece #1343 does not touch: the file-object path, where open(...).truncate(-1) raises ValueError instead of an OSError (see #1373).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions