From 7874c56301d9fd366df700732881517e3a35003a Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Mon, 17 Aug 2026 21:39:29 +1000 Subject: [PATCH] Report deletes as directory relative paths The Delete: section emitted Path.GetFileName while every other section emits a path relative to the reported directory, and the parser rebuilds each entry with Path.Combine(directory, name). UseUniqueDirectory and VerifyDirectory keep their snapshots in a subdirectory, so a stale {Directory}\Type.Method\old.verified.txt was reported as old.verified.txt and parsed back as the nonexistent {Directory}\old.verified.txt. Same named files in different subdirectories collapsed onto one entry. Also documents that the file listing paths are directory relative. --- docs/exception-message-format.md | 4 +++- docs/mdsource/exception-message-format.source.md | 2 ++ ...onParsingTests.DeleteInSubDirectory.verified.txt | 12 ++++++++++++ .../ExceptionParsingTests.cs | 13 +++++++++++++ ...geBuilderTests.DeleteInSubDirectory.verified.txt | 3 +++ .../VerifyExceptionMessageBuilderTests.cs | 10 ++++++++++ .../Verifier/VerifyExceptionMessageBuilder.cs | 5 ++++- src/todo.md | 2 +- 8 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 src/Verify.ExceptionParsing.Tests/ExceptionParsingTests.DeleteInSubDirectory.verified.txt create mode 100644 src/Verify.ExceptionParsing.Tests/VerifyExceptionMessageBuilderTests.DeleteInSubDirectory.verified.txt diff --git a/docs/exception-message-format.md b/docs/exception-message-format.md index 4812918949..79564e3bb7 100644 --- a/docs/exception-message-format.md +++ b/docs/exception-message-format.md @@ -32,6 +32,8 @@ Then zero or more categorized sections, each listing file pairs: * **InlineNew** - an [inline snapshot](inline-snapshots.md) with no expected value yet. * **InlineNotEqual** - an inline snapshot whose expected value differs from the result. +File paths in these sections are relative to the reported directory, and keep any subdirectory. `UseUniqueDirectory` and `VerifyDirectory` place snapshots in a subdirectory, so combining the reported directory with the listed path is what rebuilds the full path. + Inline entries use a different shape: a `Source:` line with the absolute source file path and 1 based line number (`path:line`), followed by optional absolute staged file paths: ``` @@ -242,7 +244,7 @@ static Result ParseExceptionMessage(string exceptionMessage) return result; } ``` -snippet source | anchor +snippet source | anchor The `Result` contains: diff --git a/docs/mdsource/exception-message-format.source.md b/docs/mdsource/exception-message-format.source.md index f9461f1c45..279b930781 100644 --- a/docs/mdsource/exception-message-format.source.md +++ b/docs/mdsource/exception-message-format.source.md @@ -25,6 +25,8 @@ Then zero or more categorized sections, each listing file pairs: * **InlineNew** - an [inline snapshot](inline-snapshots.md) with no expected value yet. * **InlineNotEqual** - an inline snapshot whose expected value differs from the result. +File paths in these sections are relative to the reported directory, and keep any subdirectory. `UseUniqueDirectory` and `VerifyDirectory` place snapshots in a subdirectory, so combining the reported directory with the listed path is what rebuilds the full path. + Inline entries use a different shape: a `Source:` line with the absolute source file path and 1 based line number (`path:line`), followed by optional absolute staged file paths: ``` diff --git a/src/Verify.ExceptionParsing.Tests/ExceptionParsingTests.DeleteInSubDirectory.verified.txt b/src/Verify.ExceptionParsing.Tests/ExceptionParsingTests.DeleteInSubDirectory.verified.txt new file mode 100644 index 0000000000..fdb0dd25b6 --- /dev/null +++ b/src/Verify.ExceptionParsing.Tests/ExceptionParsingTests.DeleteInSubDirectory.verified.txt @@ -0,0 +1,12 @@ +{ + message: +Directory: {ProjectDirectory} +Delete: + - TheType.TheMethod\old.verified.txt +, + result: { + Delete: [ + {ProjectDirectory}TheType.TheMethod\old.verified.txt + ] + } +} \ No newline at end of file diff --git a/src/Verify.ExceptionParsing.Tests/ExceptionParsingTests.cs b/src/Verify.ExceptionParsing.Tests/ExceptionParsingTests.cs index b039513881..4da7e53c99 100644 --- a/src/Verify.ExceptionParsing.Tests/ExceptionParsingTests.cs +++ b/src/Verify.ExceptionParsing.Tests/ExceptionParsingTests.cs @@ -199,6 +199,19 @@ public Task SingleDelete() return ParseVerify([], [], delete, []); } + // UseUniqueDirectory and VerifyDirectory put the verified files in a subdirectory + // of the reported directory, so the parse has to give the subdirectory back + [Fact] + public Task DeleteInSubDirectory() + { + var delete = new List + { + Path.Combine(projectDirectory, "TheType.TheMethod", "old.verified.txt") + }; + + return ParseVerify([], [], delete, []); + } + [Fact] public Task ParseInlineNew() { diff --git a/src/Verify.ExceptionParsing.Tests/VerifyExceptionMessageBuilderTests.DeleteInSubDirectory.verified.txt b/src/Verify.ExceptionParsing.Tests/VerifyExceptionMessageBuilderTests.DeleteInSubDirectory.verified.txt new file mode 100644 index 0000000000..a9bc1ee904 --- /dev/null +++ b/src/Verify.ExceptionParsing.Tests/VerifyExceptionMessageBuilderTests.DeleteInSubDirectory.verified.txt @@ -0,0 +1,3 @@ +Directory: {ProjectDirectory} +Delete: + - TheType.TheMethod\old.verified.txt diff --git a/src/Verify.ExceptionParsing.Tests/VerifyExceptionMessageBuilderTests.cs b/src/Verify.ExceptionParsing.Tests/VerifyExceptionMessageBuilderTests.cs index 768613c692..4906e53d16 100644 --- a/src/Verify.ExceptionParsing.Tests/VerifyExceptionMessageBuilderTests.cs +++ b/src/Verify.ExceptionParsing.Tests/VerifyExceptionMessageBuilderTests.cs @@ -88,6 +88,16 @@ public Task SingleDelete() => delete: [fakeReceivedTextFile], equal: []); + // UseUniqueDirectory and VerifyDirectory put the verified files in a subdirectory + // of the reported directory, which a file name would drop + [Fact] + public Task DeleteInSubDirectory() => + BuildVerify( + @new: [], + notEquals: [], + delete: [Path.Combine(projectDirectory, "TheType.TheMethod", "old.verified.txt")], + equal: []); + [Fact] public Task SingleEqual() => BuildVerify( diff --git a/src/Verify/Verifier/VerifyExceptionMessageBuilder.cs b/src/Verify/Verifier/VerifyExceptionMessageBuilder.cs index 1e4d32967e..fbaa979ce3 100644 --- a/src/Verify/Verifier/VerifyExceptionMessageBuilder.cs +++ b/src/Verify/Verifier/VerifyExceptionMessageBuilder.cs @@ -59,7 +59,10 @@ public static string Build( builder.AppendLineN("Delete:"); foreach (var file in delete) { - builder.AppendLineN($" - {Path.GetFileName(file)}"); + // directory relative, like the other sections, so the parser can + // rebuild the path. UseUniqueDirectory and VerifyDirectory put the + // verified files in a subdirectory, which a file name would drop. + builder.AppendLineN($" - {IoHelpers.GetRelativePath(directory, file)}"); } } diff --git a/src/todo.md b/src/todo.md index 03784f69b0..1a59050ae4 100644 --- a/src/todo.md +++ b/src/todo.md @@ -53,7 +53,7 @@ All six resolved 2026-08-16 (five fixed here; the inline item resolved as by-des - [ ] **MSTest overloaded test methods resolve to the wrong `MethodInfo`.** `Verify.MSTest/TestExecutionContext.cs:24-30` — `FindMethod` returns the first name match, ignoring parameters. With two `[DataRow]` overloads of one name, the parameter-count guard in `Verifier.BuildVerifier` mismatches for one of them → `SetParameters` silently skipped → both overloads collide on one snapshot prefix. -- [ ] **`Delete:` section drops subdirectories, breaking the parse round-trip.** +- [x] **`Delete:` section drops subdirectories, breaking the parse round-trip.** `Verify/Verifier/VerifyExceptionMessageBuilder.cs:62` emits `Path.GetFileName(file)` while the other sections emit directory-relative paths, and `Verify.ExceptionParsing/Parser.cs:109` reconstructs with `Path.Combine(directory, name)`. For `UseUniqueDirectory()`/`VerifyDirectory` tests, a stale `{Directory}\Type.Method\old.verified.txt` parses back as the nonexistent `{Directory}\old.verified.txt`; same-named files in different subdirectories collapse. - [ ] **`ThrowIfVerifyHasBeenRun` blames the caller instead of the API.**