diff --git a/src/Verify.Tests/Comparer/HandleStreamTests.Mismatch.verified.bin b/src/Verify.Tests/Comparer/HandleStreamTests.Mismatch.verified.bin new file mode 100644 index 000000000..7e9dfa3f8 --- /dev/null +++ b/src/Verify.Tests/Comparer/HandleStreamTests.Mismatch.verified.bin @@ -0,0 +1 @@ +TheVerifiedValue \ No newline at end of file diff --git a/src/Verify.Tests/Comparer/HandleStreamTests.cs b/src/Verify.Tests/Comparer/HandleStreamTests.cs new file mode 100644 index 000000000..ad7d58b34 --- /dev/null +++ b/src/Verify.Tests/Comparer/HandleStreamTests.cs @@ -0,0 +1,24 @@ +// A FileStream built from a handle may have no usable Name: .NET Framework reports +// "[Unknown]", and modern .NET falls back to that when the path cannot be resolved +// from the handle. The New path already copes, so only a mismatch exercised it. +public class HandleStreamTests +{ + [Fact] + public async Task Mismatch() + { + using var directory = new TempDirectory(); + var path = directory.BuildPath("source.txt"); + File.WriteAllText(path, "TheReceivedValue"); + + using var source = File.OpenRead(path); + using var stream = new FileStream(source.SafeFileHandle, FileAccess.Read); + + var settings = new VerifySettings(); + settings.DisableDiff(); + + await Assert.ThrowsAsync(() => Verify(stream, "bin", settings)); + + var received = CurrentFile.Relative($"HandleStreamTests.Mismatch.{Namer.RuntimeAndVersion}.received.bin"); + Assert.Equal("TheReceivedValue", File.ReadAllText(received)); + } +} diff --git a/src/Verify/Compare/FileComparer.cs b/src/Verify/Compare/FileComparer.cs index 3d6009751..e42798add 100644 --- a/src/Verify/Compare/FileComparer.cs +++ b/src/Verify/Compare/FileComparer.cs @@ -47,7 +47,10 @@ static async Task InnerCompare(FilePair file, Stream receivedStr return new(Equality.Equal, compareResult.Message, null, null); } - IoHelpers.CopyFile(fileStream.Name, file.ReceivedPath); + // Not CopyFile(fileStream.Name): a FileStream built from a handle has no + // usable Name. WriteStream keeps the copy-by-path fast path and falls back + // to the handle, which is what the New and empty paths above already do. + await IoHelpers.WriteStream(file.ReceivedPath, fileStream); return new(Equality.NotEqual, compareResult.Message, null, null); } diff --git a/src/todo.md b/src/todo.md index 03784f69b..a10ef720c 100644 --- a/src/todo.md +++ b/src/todo.md @@ -76,7 +76,7 @@ All six resolved 2026-08-16 (five fixed here; the inline item resolved as by-des - [ ] **Negative sub-hour offsets render unsigned.** `Verify/Serialization/DateFormatter_DateTimeOffset.cs:74-82` — `TimeSpan.FromMinutes(-30)` renders `0-30` (positive twin is `+0-30`). No real timezone in that range; constructed offsets only. -- [ ] **Mismatch crash for handle-based `FileStream` received streams.** +- [x] **Mismatch crash for handle-based `FileStream` received streams.** `Verify/Compare/FileComparer.cs:50` — NotEqual fast path copies by `fileStream.Name` with no fallback; handle-based streams have `Name == "[Unknown]"`. First (New) run succeeds via the guarded `IoHelpers.WriteStream` path; later mismatches throw the generic "Failed to compare files". - [ ] **`PrefixUnique` set is case-sensitive on case-insensitive filesystems.**