Skip to content

Don't trust a zero Windows file ID in samefile_nofollow - #14865

Open
RonnyPfannschmidt wants to merge 1 commit into
pytest-dev:mainfrom
RonnyPfannschmidt:fix-14864-samefile-zero-file-id
Open

Don't trust a zero Windows file ID in samefile_nofollow#14865
RonnyPfannschmidt wants to merge 1 commit into
pytest-dev:mainfrom
RonnyPfannschmidt:fix-14864-samefile-zero-file-id

Conversation

@RonnyPfannschmidt

Copy link
Copy Markdown
Member

Fixes #14864.

On Windows, when a collection argument's path does not compare equal to a collected node's path, Session.collect() falls back to samefile_nofollow() to account for 8.3 short paths (#11895). That fallback uses os.path.samestat(), i.e. it compares (st_dev, st_ino).

On Windows st_ino is the file ID, and support for file IDs is file-system specific — a file system that has none reports 0 for every file, the same convention the neighbouring BY_HANDLE_FILE_INFORMATION fields use for unsupported values. The reporter's drive is an sshfs (WinFsp) mount, where the chain is:

  • SFTP v3's ATTRS has no inode field, so sshfs never fills st_ino (the identifier does not appear in sshfs.c at all).
  • On Linux this is invisible because libfuse's set_stat() overwrites st_ino with its own node id unless use_ino is given. WinFsp's FUSE layer has no such fallback: it discards use_ino and assigns FileInfo->IndexNumber = stbuf.st_ino verbatim.
  • FILE_ID_INFO.FileId and nFileIndexHigh/Low are therefore both 0, and CPython passes that straight through to st_ino.

So os.path.samestat() reports every pair of files on the volume as the same file, every node matched, nothing got pruned, and passing a single file collected the whole suite. This is the long-standing python/cpython#78116 (samefile() should not use zero-valued st_dev and st_ino), which also affects Google Drive File Stream and WebDAV volumes.

Fixed by treating a zero file ID as "unknown" in the Windows version of samefile_nofollow(), so the caller falls back to plain path comparison — which is what every non-Windows platform does anyway, since the samestat() fallback is win32-only. The short-path case from #11895 is local NTFS with real file IDs and keeps working; 8.3 short names are an NTFS/FAT feature that the affected mounts do not generate in the first place.

Note that _is_same() in the same module has the same weakness, but there a bogus True only makes the import-mismatch check miss a mismatch rather than break collection, so I left it alone.

🤖 Generated with Claude Code

@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Aug 12, 2026
@RonnyPfannschmidt RonnyPfannschmidt added the backport 9.1.x apply to PRs at any point; backports the changes to the 9.1.x branch label Aug 12, 2026
On Windows st_ino holds the file ID, which file systems are free to not
support; WinFsp mounts such as sshfs-win report 0 for every file, because
SFTP has no inode field to fill it from. os.path.samestat() then reports
any two files on the volume as the same file, so the Windows short-path
fallback in Session.collect() matched every node and nothing got pruned:
passing a single file collected the whole suite.

Treat a zero file ID as unknown and fall back to path comparison, which
is what the other platforms do anyway.

Fixes pytest-dev#14864.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@bluetech bluetech left a comment

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.

LGTM!

Comment thread src/_pytest/pathlib.py

Unlike Path.samefile(), does not resolve symlinks.

On Windows st_ino is the file ID, which file systems are free to not

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 would put this in a code comment, not in the docstring (it's an implementation detail IMO).

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

Labels

backport 9.1.x apply to PRs at any point; backports the changes to the 9.1.x branch bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(Windows) passing a file path collects the whole suite, on a network drive where st_ino is 0

2 participants