Skip to content

fix: read MockFileInfo/MockDirectoryInfo state lazily like FileSystemInfo - #1547

Open
Mpdreamz wants to merge 1 commit into
TestableIO:mainfrom
Mpdreamz:fix/filesysteminfo-lazy-snapshot
Open

Mpdreamz wants to merge 1 commit into
TestableIO:mainfrom
Mpdreamz:fix/filesysteminfo-lazy-snapshot

Conversation

@Mpdreamz

Copy link
Copy Markdown

The real FileSystemInfo does not read a file's state when it is constructed. It takes one snapshot on the first read of any cached property (Exists, Length, Attributes, the times), and serves every property from that snapshot until Refresh(), or until an operation on the same instance invalidates it. MockFileInfo and MockDirectoryInfo take that snapshot in their constructor instead. As a result, a test that creates the info object first and changes the file afterwards sees something the real file system never reports:

var fileInfo = fileSystem.FileInfo.New(path);
fileSystem.File.WriteAllText(path, "x");
fileInfo.Exists; // real: true, MockFileSystem: false

Code that passes against a real disk fails against the mock unless it adds a Refresh() that production code doesn't need.

Change

  • The constructors now only mark the snapshot as pending, using the existing refreshOnNextRead flag. The snapshot is taken on first access and cached exactly as before. Refresh() clears the pending flag.
  • Instances returned by DirectoryInfo enumeration are refreshed while enumerating, as the runtime fills their snapshot from the enumeration data.
  • MoveTo discards the snapshot, as it does in the runtime.
  • There is no public API change.

Validated against the real file system

I ran 21 scenarios through new FileSystem() and new MockFileSystem() on .NET 10 and compared the results.

Scenario Real Before After
FileInfo created, then file written, Exists True False True
Exists read, file written, Exists again False → False False → False False → False
… then Refresh() True True True
File exists, FileInfo created, file deleted, Exists False True False
FileInfo created, file grows, Length 10 3 10
Length read, file grows, Length again 3 → 3 3 → 3 3 → 3
FileInfo created before the file, Length 3 FileNotFoundException 3
FileInfo.Create() / Delete() then Exists True / False True / False True / False
DirectoryInfo created, then directory created, Exists True False True
DirectoryInfo on existing directory, deleted, Exists False True False
Attributes / LastWriteTime changed before first read new old new
Attributes / LastWriteTime read, changed, read again old → old old → old old → old
Exists read, file grows, Length 3 3 3
FileInfo from GetFiles(), file deleted, Exists True True True
Length read, file grows, MoveTo, Length 10 3 10
DirectoryInfo attributes read, changed, MoveTo, attributes new old new

Tests

  • 20 new tests in MockFileInfoTests and MockDirectoryInfoTests. 12 of them fail on the current code, one for each scenario that was wrong.
  • The two …_Exists_ShouldReturnCachedData tests asserted the snapshot from construction time. They now read Exists once before the change, so they still test that the snapshot stays cached after the first read.

In Release on net8.0, net9.0 and net10.0 (macOS): TestingHelpers, Wrappers, API and parity tests all pass.

Worth knowing

Code that relied on the construction-time snapshot will now see the current state. For example: creating a FileInfo, changing the file, and expecting the old values. MockFileSystem.AddFile(IFileInfo, …) still refreshes the instance it is given, so that path is unchanged.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TymoqrVYgZwdNSper5ZZB1

…Info

The real FileSystemInfo does not read the file's state when it is
constructed. It takes one snapshot on the first read of a cached property
(Exists, Length, Attributes, the times) and serves every property from it
until Refresh() or an operation on the same instance invalidates it.

MockFileInfo and MockDirectoryInfo took the snapshot in their constructor,
so an instance created before a file or directory was created, deleted or
changed reported the state from construction time: Exists false for a file
created afterwards, Exists true for one deleted afterwards, stale Length,
Attributes and times, and FileNotFoundException from Length for a file
that exists by the time it is read.

The constructors now only mark the snapshot as pending, so it is taken on
first access and then cached as before; Refresh() clears the pending flag.
Instances returned by DirectoryInfo enumeration are refreshed while
enumerating, matching the real enumeration, which fills the snapshot from
the enumeration data. MoveTo discards the snapshot, as it does in the
runtime.

The two "ShouldReturnCachedData" tests asserted the construction-time
snapshot; they now read Exists once before the change so they keep
testing that the snapshot is cached after the first read.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TymoqrVYgZwdNSper5ZZB1
Copilot AI balanced review requested due to automatic review settings September 30, 2026 15:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The implementation consistently matches the described runtime semantics and includes comprehensive regression coverage.

Review effort: Balanced
Findings: None

What changed in this PR

Aligns mock file-system metadata caching with real FileSystemInfo lazy snapshot behavior.

Changes:

  • Defers metadata snapshots until first cached-property access.
  • Invalidates snapshots after moves.
  • Preserves enumeration-time snapshots and adds regression tests.
File Description
MockFileInfo.cs Implements lazy refresh and move invalidation.
MockDirectoryInfo.cs Implements lazy refresh, enumeration snapshots, and move invalidation.
MockFileInfoTests.cs Tests lazy and cached file metadata behavior.
MockDirectoryInfoTests.cs Tests lazy and cached directory metadata behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@mergify

mergify Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants