From aef5b353f0a17ab6d8c37cca51c2543afa24043d Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Mon, 17 Aug 2026 20:20:27 +1000 Subject: [PATCH] Clean # out of file name parameters `{prefix}#` is the indexed-target namespace, and MatchingFileFinder matches it by prefix, but `#` was not cleaned out of parameter values. With cases "x" and "x#1" on one method, running "x" deleted `C.M_p=x#1.received.txt` and swept `C.M_p=x#1.verified.txt` into the stale set, which deletes it under AutoVerify. The two cannot be told apart after the fact, since `C.M_p=x#1.verified.txt` is exactly what an indexed target of `C.M_p=x` looks like, so `#` now joins the characters FileNameCleaner replaces with `-`. --- .../Naming/NameForParameterTests.StringHash.verified.txt | 1 + src/Verify.Tests/Naming/NameForParameterTests.cs | 6 ++++++ .../NamerTests.ParametersWithHash_value=x-1.verified.txt | 1 + .../NamerTests.ParametersWithHash_value=x.verified.txt | 1 + src/Verify.Tests/Naming/NamerTests.cs | 9 +++++++++ src/Verify/FileNameCleaner.cs | 5 ++++- src/todo.md | 2 +- 7 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/Verify.Tests/Naming/NameForParameterTests.StringHash.verified.txt create mode 100644 src/Verify.Tests/Naming/NamerTests.ParametersWithHash_value=x-1.verified.txt create mode 100644 src/Verify.Tests/Naming/NamerTests.ParametersWithHash_value=x.verified.txt diff --git a/src/Verify.Tests/Naming/NameForParameterTests.StringHash.verified.txt b/src/Verify.Tests/Naming/NameForParameterTests.StringHash.verified.txt new file mode 100644 index 0000000000..147b3b5661 --- /dev/null +++ b/src/Verify.Tests/Naming/NameForParameterTests.StringHash.verified.txt @@ -0,0 +1 @@ +a-1 \ No newline at end of file diff --git a/src/Verify.Tests/Naming/NameForParameterTests.cs b/src/Verify.Tests/Naming/NameForParameterTests.cs index d006f67f77..bce0802e74 100644 --- a/src/Verify.Tests/Naming/NameForParameterTests.cs +++ b/src/Verify.Tests/Naming/NameForParameterTests.cs @@ -12,6 +12,12 @@ public Task StringEmpty() => public Task StringInvalidPathChar() => Verify(VerifierSettings.GetNameForParameter("a/a", counter: CounterBuilder.Empty())); + // `#` is reserved for the indexed-target namespace, so a value containing one + // would make this case's files look like the targets of the case without it + [Fact] + public Task StringHash() => + Verify(VerifierSettings.GetNameForParameter("a#1", counter: CounterBuilder.Empty())); + [Fact] public void CollectionItemPathFriendlyFalseNotCleaned() { diff --git a/src/Verify.Tests/Naming/NamerTests.ParametersWithHash_value=x-1.verified.txt b/src/Verify.Tests/Naming/NamerTests.ParametersWithHash_value=x-1.verified.txt new file mode 100644 index 0000000000..1829444fc6 --- /dev/null +++ b/src/Verify.Tests/Naming/NamerTests.ParametersWithHash_value=x-1.verified.txt @@ -0,0 +1 @@ +x#1 \ No newline at end of file diff --git a/src/Verify.Tests/Naming/NamerTests.ParametersWithHash_value=x.verified.txt b/src/Verify.Tests/Naming/NamerTests.ParametersWithHash_value=x.verified.txt new file mode 100644 index 0000000000..b18d454135 --- /dev/null +++ b/src/Verify.Tests/Naming/NamerTests.ParametersWithHash_value=x.verified.txt @@ -0,0 +1 @@ +x \ No newline at end of file diff --git a/src/Verify.Tests/Naming/NamerTests.cs b/src/Verify.Tests/Naming/NamerTests.cs index cb8702c193..725fc47c53 100644 --- a/src/Verify.Tests/Naming/NamerTests.cs +++ b/src/Verify.Tests/Naming/NamerTests.cs @@ -491,6 +491,15 @@ public Task ParametersWithBadPathChars(string value) => Verify(value) .UseParameters(value); + // `{prefix}#` is the indexed-target namespace, so without cleaning the `x` case + // would claim the files of the `x#1` case + [Theory] + [InlineData("x")] + [InlineData("x#1")] + public Task ParametersWithHash(string value) => + Verify(value) + .UseParameters(value); + [Fact] public Task SingleTarget() => Verify([new Target("txt", "data")]); diff --git a/src/Verify/FileNameCleaner.cs b/src/Verify/FileNameCleaner.cs index b7f987e006..2d34a19c7e 100644 --- a/src/Verify/FileNameCleaner.cs +++ b/src/Verify/FileNameCleaner.cs @@ -42,7 +42,10 @@ ':', '*', '?', - '/' + '/', + // Not invalid on disk, but reserved: `{prefix}#` is the indexed-target namespace, + // so a `#` in a value would make one case's files look like another case's targets + '#' ]; static char[] invalidPathChars = diff --git a/src/todo.md b/src/todo.md index 03784f69b0..b3b8b883a6 100644 --- a/src/todo.md +++ b/src/todo.md @@ -35,7 +35,7 @@ All six resolved 2026-08-16 (five fixed here; the inline item resolved as by-des - [ ] **Sub-millisecond date parameters collide.** `Verify/Serialization/DateFormatter_DateTime.cs` (and the `DateTimeOffset` twin) use `Second == 0` / `Millisecond == 0` to omit the fraction, but sub-millisecond ticks leave those properties 0. `AddTicks(1)` and `AddTicks(2)` cases format identically → spurious "prefix has already been used" (or silent sharing of one verified file). Correct check is ticks-based. -- [ ] **`#` in parameter values collides with the indexed-target namespace.** +- [x] **`#` in parameter values collides with the indexed-target namespace.** `Verify/Naming/MatchingFileFinder.cs:9,20` — `indexedPattern: "{prefix}#"` matches by prefix, and `#` is not sanitized. Cases `"x"` and `"x#1"` on one method: running `"x"` deletes `C.M_p=x#1.received.txt` and sweeps `C.M_p=x#1.verified.txt` into the stale set (deleted under AutoVerify). - [ ] **Trimmed fraction format collapses into a standard format specifier.**