Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

45 changes: 45 additions & 0 deletions src/Verify.Tests/Naming/MatchingFileFinderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,49 @@ public void StrayFileWithNoExtensionIsIgnored()
Assert.Single(found);
Assert.EndsWith("SomeTest.verified.txt", found[0]);
}

// A target name can contain a directory separator, which puts the file below a
// `{prefix}#` directory where a flat scan cannot see it
[Fact]
public void FindVerifiedIncludesNested()
{
using var directory = new TempDirectory();
File.WriteAllText(directory.BuildPath("SomeTest.verified.txt"), "a");
var nested = WriteNested(directory, "SomeTest#sub", "inner", "file.verified.txt");
// not a file Verify writes, and the `.*` in the search pattern matches it
WriteNested(directory, "SomeTest#sub", "extensionless.verified");
// a different test that happens to start with the same text
var otherNested = WriteNested(directory, "SomeTestOther#sub", "file.verified.txt");

var found = MatchingFileFinder.FindVerified("SomeTest", directory)
.ToList();

Assert.Equal(2, found.Count);
Assert.Contains(nested, found);
Assert.DoesNotContain(otherNested, found);
}

[Fact]
public void DeleteReceivedIncludesNested()
{
using var directory = new TempDirectory();
var nestedReceived = WriteNested(directory, "SomeTest#sub", "inner", "file.received.txt");
var nestedVerified = WriteNested(directory, "SomeTest#sub", "inner", "file.verified.txt");
var otherReceived = WriteNested(directory, "SomeTestOther#sub", "file.received.txt");

MatchingFileFinder.DeleteReceived("SomeTest", directory);

Assert.False(File.Exists(nestedReceived));
// only received files are swept, and only for this test
Assert.True(File.Exists(nestedVerified));
Assert.True(File.Exists(otherReceived));
}

static string WriteNested(TempDirectory directory, params string[] segments)
{
var path = Path.Combine([directory.Path, ..segments]);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
File.WriteAllText(path, "content");
return path;
}
}
53 changes: 39 additions & 14 deletions src/Verify/Naming/MatchingFileFinder.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
static class MatchingFileFinder
static class MatchingFileFinder
{
public static void DeleteReceived(string fileNamePrefix, string directory)
{
foreach (var file in Find(
directory,
searchPattern: $"{fileNamePrefix}*.received.*",
nonIndexedPattern: $"{fileNamePrefix}.received.",
indexedPattern: $"{fileNamePrefix}#"))
foreach (var file in Find(directory, fileNamePrefix, "received"))
{
IoHelpers.DeleteFile(file);
}
}

public static IEnumerable<string> FindVerified(string fileNamePrefix, string directory) =>
Find(
directory,
searchPattern: $"{fileNamePrefix}*.verified.*",
nonIndexedPattern: $"{fileNamePrefix}.verified.",
indexedPattern: $"{fileNamePrefix}#");
Find(directory, fileNamePrefix, "verified");

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

var indexedPattern = $"{fileNamePrefix}#";
var nonIndexedPattern = $"{fileNamePrefix}.{marker}.";

var list = new List<string>();
var nonIndexedPatternSpan = nonIndexedPattern.AsSpan();
var indexedPatternSpan = indexedPattern.AsSpan();
foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
foreach (var file in Directory.EnumerateFiles(directory, $"{fileNamePrefix}*.{marker}.*"))
{
var fileSpan = file.AsSpan();
if (fileSpan.SubStringEquals(nonIndexedPatternSpan, startIndex) ||
Expand All @@ -45,9 +40,39 @@ static List<string> Find(string directory, string searchPattern, string nonIndex
}
}

AddNested(directory, indexedPattern, marker, list);

return list;
}

/// <summary>
/// A target name can contain a directory separator: VerifyDirectory names each target
/// after its path within the tree, and SanitizeFilePath deliberately keeps separators.
/// The file for such a target lives under a `{prefix}#` directory, which the flat scan
/// cannot see, so without this its verified file is never tracked and a stale one
/// survives every run.
/// </summary>
static void AddNested(string directory, string indexedPattern, string marker, List<string> list)
{
var markerSegment = $".{marker}.";
// The directory name carries the prefix, so everything below it belongs to this
// test. A neighbouring test that merely starts with the same text has its own
// `{itsPrefix}#` directory, which this pattern excludes.
foreach (var subDirectory in Directory.EnumerateDirectories(directory, $"{indexedPattern}*"))
{
foreach (var file in Directory.EnumerateFiles(subDirectory, $"*{markerSegment}*", SearchOption.AllDirectories))
{
// The Win32 pattern also matches a name ending in `.verified` with no
// extension, which is not a file Verify writes.
if (Path.GetFileName(file)
.Contains(markerSegment))
{
list.Add(file);
}
}
}
}

static bool SubStringEquals(this CharSpan value, CharSpan match, int start)
{
// The Win32 search pattern `{prefix}*.received.*` also matches a stray file named
Expand All @@ -61,4 +86,4 @@ static bool SubStringEquals(this CharSpan value, CharSpan match, int start)
var slice = value.Slice(start, match.Length);
return slice.SequenceEqual(match);
}
}
}
Loading