Skip to content

Commit 27db6b6

Browse files
authored
Track verified files nested under the indexed target namespace (#1888)
A target name may contain a directory separator: VerifyDirectory names each target after its path within the tree, and SanitizeFilePath deliberately keeps separators. Those files land under a '{prefix}#' directory, which the flat EnumerateFiles scan could not see. So a verified file below that directory was never added to the stale set, and removing or renaming a file inside a verified tree left its snapshot behind with the test still green. Received files in the same place were never swept either. The scan now also walks '{prefix}#' subdirectories. The prefix is carried by the directory name, so a neighbouring test whose name merely starts with the same text keeps its own namespace. Deletes the one file in this repo that the gap was hiding: an orphan added by #1547 alongside the sanitized name that replaced it. The suite now reports it, which is how it was found.
1 parent 8890b5c commit 27db6b6

3 files changed

Lines changed: 84 additions & 15 deletions

File tree

src/Verify.Tests/Converters/ExtensionConverterTests.InvalidFileCharactersInName#name/a/b-c.verified.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/Verify.Tests/Naming/MatchingFileFinderTests.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,49 @@ public void StrayFileWithNoExtensionIsIgnored()
4444
Assert.Single(found);
4545
Assert.EndsWith("SomeTest.verified.txt", found[0]);
4646
}
47+
48+
// A target name can contain a directory separator, which puts the file below a
49+
// `{prefix}#` directory where a flat scan cannot see it
50+
[Fact]
51+
public void FindVerifiedIncludesNested()
52+
{
53+
using var directory = new TempDirectory();
54+
File.WriteAllText(directory.BuildPath("SomeTest.verified.txt"), "a");
55+
var nested = WriteNested(directory, "SomeTest#sub", "inner", "file.verified.txt");
56+
// not a file Verify writes, and the `.*` in the search pattern matches it
57+
WriteNested(directory, "SomeTest#sub", "extensionless.verified");
58+
// a different test that happens to start with the same text
59+
var otherNested = WriteNested(directory, "SomeTestOther#sub", "file.verified.txt");
60+
61+
var found = MatchingFileFinder.FindVerified("SomeTest", directory)
62+
.ToList();
63+
64+
Assert.Equal(2, found.Count);
65+
Assert.Contains(nested, found);
66+
Assert.DoesNotContain(otherNested, found);
67+
}
68+
69+
[Fact]
70+
public void DeleteReceivedIncludesNested()
71+
{
72+
using var directory = new TempDirectory();
73+
var nestedReceived = WriteNested(directory, "SomeTest#sub", "inner", "file.received.txt");
74+
var nestedVerified = WriteNested(directory, "SomeTest#sub", "inner", "file.verified.txt");
75+
var otherReceived = WriteNested(directory, "SomeTestOther#sub", "file.received.txt");
76+
77+
MatchingFileFinder.DeleteReceived("SomeTest", directory);
78+
79+
Assert.False(File.Exists(nestedReceived));
80+
// only received files are swept, and only for this test
81+
Assert.True(File.Exists(nestedVerified));
82+
Assert.True(File.Exists(otherReceived));
83+
}
84+
85+
static string WriteNested(TempDirectory directory, params string[] segments)
86+
{
87+
var path = Path.Combine([directory.Path, ..segments]);
88+
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
89+
File.WriteAllText(path, "content");
90+
return path;
91+
}
4792
}
Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
1-
static class MatchingFileFinder
1+
static class MatchingFileFinder
22
{
33
public static void DeleteReceived(string fileNamePrefix, string directory)
44
{
5-
foreach (var file in Find(
6-
directory,
7-
searchPattern: $"{fileNamePrefix}*.received.*",
8-
nonIndexedPattern: $"{fileNamePrefix}.received.",
9-
indexedPattern: $"{fileNamePrefix}#"))
5+
foreach (var file in Find(directory, fileNamePrefix, "received"))
106
{
117
IoHelpers.DeleteFile(file);
128
}
139
}
1410

1511
public static IEnumerable<string> FindVerified(string fileNamePrefix, string directory) =>
16-
Find(
17-
directory,
18-
searchPattern: $"{fileNamePrefix}*.verified.*",
19-
nonIndexedPattern: $"{fileNamePrefix}.verified.",
20-
indexedPattern: $"{fileNamePrefix}#");
12+
Find(directory, fileNamePrefix, "verified");
2113

22-
static List<string> Find(string directory, string searchPattern, string nonIndexedPattern, string indexedPattern)
14+
static List<string> Find(string directory, string fileNamePrefix, string marker)
2315
{
2416
// Directory.EnumerateFiles inserts a separator only when the directory
2517
// does not already end with one, so the file-name offset depends on
@@ -32,10 +24,13 @@ static List<string> Find(string directory, string searchPattern, string nonIndex
3224
startIndex++;
3325
}
3426

27+
var indexedPattern = $"{fileNamePrefix}#";
28+
var nonIndexedPattern = $"{fileNamePrefix}.{marker}.";
29+
3530
var list = new List<string>();
3631
var nonIndexedPatternSpan = nonIndexedPattern.AsSpan();
3732
var indexedPatternSpan = indexedPattern.AsSpan();
38-
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
33+
foreach (var file in Directory.EnumerateFiles(directory, $"{fileNamePrefix}*.{marker}.*"))
3934
{
4035
var fileSpan = file.AsSpan();
4136
if (fileSpan.SubStringEquals(nonIndexedPatternSpan, startIndex) ||
@@ -45,9 +40,39 @@ static List<string> Find(string directory, string searchPattern, string nonIndex
4540
}
4641
}
4742

43+
AddNested(directory, indexedPattern, marker, list);
44+
4845
return list;
4946
}
5047

48+
/// <summary>
49+
/// A target name can contain a directory separator: VerifyDirectory names each target
50+
/// after its path within the tree, and SanitizeFilePath deliberately keeps separators.
51+
/// The file for such a target lives under a `{prefix}#` directory, which the flat scan
52+
/// cannot see, so without this its verified file is never tracked and a stale one
53+
/// survives every run.
54+
/// </summary>
55+
static void AddNested(string directory, string indexedPattern, string marker, List<string> list)
56+
{
57+
var markerSegment = $".{marker}.";
58+
// The directory name carries the prefix, so everything below it belongs to this
59+
// test. A neighbouring test that merely starts with the same text has its own
60+
// `{itsPrefix}#` directory, which this pattern excludes.
61+
foreach (var subDirectory in Directory.EnumerateDirectories(directory, $"{indexedPattern}*"))
62+
{
63+
foreach (var file in Directory.EnumerateFiles(subDirectory, $"*{markerSegment}*", SearchOption.AllDirectories))
64+
{
65+
// The Win32 pattern also matches a name ending in `.verified` with no
66+
// extension, which is not a file Verify writes.
67+
if (Path.GetFileName(file)
68+
.Contains(markerSegment))
69+
{
70+
list.Add(file);
71+
}
72+
}
73+
}
74+
}
75+
5176
static bool SubStringEquals(this CharSpan value, CharSpan match, int start)
5277
{
5378
// The Win32 search pattern `{prefix}*.received.*` also matches a stray file named
@@ -61,4 +86,4 @@ static bool SubStringEquals(this CharSpan value, CharSpan match, int start)
6186
var slice = value.Slice(start, match.Length);
6287
return slice.SequenceEqual(match);
6388
}
64-
}
89+
}

0 commit comments

Comments
 (0)