From e7afcda8bc65a0f8d9c2918696afa5eed0111375 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 17 Feb 2021 23:19:21 -0800 Subject: [PATCH] Clarify the behavior of FilePatternMatch Path and Stem (#48093) * Add theories that illustrate globbing Path/Stem behavior * Update Path/Stem docs for FilePatternMatch * Apply suggestions from code review Co-authored-by: Carlos Sanchez <1175054+carlossanlop@users.noreply.github.com> Co-authored-by: Carlos Sanchez <1175054+carlossanlop@users.noreply.github.com> --- .../src/FilePatternMatch.cs | 17 +- .../tests/FunctionalTests.cs | 193 ++++++++++++++++++ 2 files changed, 201 insertions(+), 9 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/FilePatternMatch.cs b/src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/FilePatternMatch.cs index aa554523477f3..21d291a243416 100644 --- a/src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/FilePatternMatch.cs +++ b/src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/FilePatternMatch.cs @@ -12,29 +12,28 @@ namespace Microsoft.Extensions.FileSystemGlobbing public struct FilePatternMatch : IEquatable { /// - /// The path to the file matched + /// The path to the file matched, relative to the beginning of the matching search pattern. /// /// - /// If the matcher searched for "**/*.cs" using "src/Project" as the directory base and the pattern matcher found - /// "src/Project/Interfaces/IFile.cs", then Stem = "Interfaces/IFile.cs" and Path = "src/Project/Interfaces/IFile.cs". + /// If the matcher searched for "src/Project/**/*.cs" and the pattern matcher found "src/Project/Interfaces/IFile.cs", + /// then = "Interfaces/IFile.cs" and = "src/Project/Interfaces/IFile.cs". /// public string Path { get; } /// - /// The subpath to the matched file under the base directory searched + /// The subpath to the file matched, relative to the first wildcard in the matching search pattern. /// /// - /// If the matcher searched for "**/*.cs" using "src/Project" as the directory base and the pattern matcher found - /// "src/Project/Interfaces/IFile.cs", - /// then Stem = "Interfaces/IFile.cs" and Path = "src/Project/Interfaces/IFile.cs". + /// If the matcher searched for "src/Project/**/*.cs" and the pattern matcher found "src/Project/Interfaces/IFile.cs", + /// then = "Interfaces/IFile.cs" and = "src/Project/Interfaces/IFile.cs". /// public string Stem { get; } /// /// Initializes new instance of /// - /// The path to the matched file - /// The stem + /// The path to the file matched, relative to the beginning of the matching search pattern. + /// The subpath to the file matched, relative to the first wildcard in the matching search pattern. public FilePatternMatch(string path, string stem) { Path = path; diff --git a/src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/FunctionalTests.cs b/src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/FunctionalTests.cs index b52e1cb94e925..ce56c09bc1a72 100644 --- a/src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/FunctionalTests.cs +++ b/src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/FunctionalTests.cs @@ -445,10 +445,203 @@ public void MultipleSubDirsAfterFirstWildcardMatch_HasCorrectStem_WithInMemory() AssertExtensions.CollectionEqual(expected, actual, StringComparer.OrdinalIgnoreCase); } + [Theory] // rootDir, includePattern, expectedPath + [InlineData(@"root", @"*.0", @"test.0")] + [InlineData(@"root", @"**/*.0", @"test.0")] + public void PathIncludesAllSegmentsFromPattern_RootDirectory(string root, string includePattern, string expectedPath) + { + var fileToFind = @"root/test.0"; + + var matcher = new Matcher(); + matcher.AddInclude(includePattern); + + var results = matcher.Match(root, new[] { fileToFind }); + var actualPath = results.Files.Select(file => file.Path).SingleOrDefault(); + + Assert.Equal(expectedPath, actualPath); + + // Also test all scenarios with the `./` current directory prefix + matcher = new Matcher(); + matcher.AddInclude("./" + includePattern); + + results = matcher.Match(root, new[] { fileToFind }); + actualPath = results.Files.Select(file => file.Path).SingleOrDefault(); + + Assert.Equal(expectedPath, actualPath); + } + + [Theory] // rootDir, includePattern, expectedPath + [InlineData(@"root/dir1", @"*.1", @"test.1")] + [InlineData(@"root/dir1", @"**/*.1", @"test.1")] + [InlineData(@"root", @"dir1/*.1", @"dir1/test.1")] + [InlineData(@"root", @"dir1/**/*.1", @"dir1/test.1")] + [InlineData(@"root", @"**/dir1/*.1", @"dir1/test.1")] + [InlineData(@"root", @"**/dir1/**/*.1", @"dir1/test.1")] + [InlineData(@"root", @"**/*.1", @"dir1/test.1")] + public void PathIncludesAllSegmentsFromPattern_OneDirectoryDeep(string root, string includePattern, string expectedPath) + { + var fileToFind = @"root/dir1/test.1"; + + var matcher = new Matcher(); + matcher.AddInclude(includePattern); + + var results = matcher.Match(root, new[] { fileToFind }); + var actualPath = results.Files.Select(file => file.Path).SingleOrDefault(); + + Assert.Equal(expectedPath, actualPath); + + // Also test all scenarios with the `./` current directory prefix + matcher = new Matcher(); + matcher.AddInclude("./" + includePattern); + + results = matcher.Match(root, new[] { fileToFind }); + actualPath = results.Files.Select(file => file.Path).SingleOrDefault(); + + Assert.Equal(expectedPath, actualPath); + } + + [Theory] // rootDir, includePattern, expectedPath + [InlineData(@"root/dir1/dir2", @"*.2", @"test.2")] + [InlineData(@"root/dir1/dir2", @"**/*.2", @"test.2")] + [InlineData(@"root/dir1", @"dir2/*.2", @"dir2/test.2")] + [InlineData(@"root/dir1", @"dir2/**/*.2", @"dir2/test.2")] + [InlineData(@"root/dir1", @"**/dir2/*.2", @"dir2/test.2")] + [InlineData(@"root/dir1", @"**/dir2/**/*.2", @"dir2/test.2")] + [InlineData(@"root/dir1", @"**/*.2", @"dir2/test.2")] + [InlineData(@"root", @"dir1/dir2/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"dir1/dir2/**/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"**/dir1/dir2/**/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"**/dir1/**/dir2/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"**/dir1/**/dir2/**/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"dir1/**/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"**/dir1/**/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"**/dir2/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"**/dir2/**/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"**/*.2", @"dir1/dir2/test.2")] + public void PathIncludesAllSegmentsFromPattern_TwoDirectoriesDeep(string root, string includePattern, string expectedPath) + { + var fileToFind = @"root/dir1/dir2/test.2"; + + var matcher = new Matcher(); + matcher.AddInclude(includePattern); + + var results = matcher.Match(root, new[] { fileToFind }); + var actualPath = results.Files.Select(file => file.Path).SingleOrDefault(); + + Assert.Equal(expectedPath, actualPath); + + // Also test all scenarios with the `./` current directory prefix + matcher = new Matcher(); + matcher.AddInclude("./" + includePattern); + + results = matcher.Match(root, new[] { fileToFind }); + actualPath = results.Files.Select(file => file.Path).SingleOrDefault(); + + Assert.Equal(expectedPath, actualPath); + } + + [Theory] // rootDir, includePattern, expectedStem + [InlineData(@"root", @"*.0", @"test.0")] + [InlineData(@"root", @"**/*.0", @"test.0")] + public void StemIncludesAllSegmentsFromPatternStartingAtWildcard_RootDirectory(string root, string includePattern, string expectedStem) + { + var fileToFind = @"root/test.0"; + + var matcher = new Matcher(); + matcher.AddInclude(includePattern); + + var results = matcher.Match(root, new[] { fileToFind }); + var actualStem = results.Files.Select(file => file.Stem).SingleOrDefault(); + + Assert.Equal(expectedStem, actualStem); + + // Also test all scenarios with the `./` current directory prefix + matcher = new Matcher(); + matcher.AddInclude("./" + includePattern); + + results = matcher.Match(root, new[] { fileToFind }); + actualStem = results.Files.Select(file => file.Stem).SingleOrDefault(); + + Assert.Equal(expectedStem, actualStem); + } + + [Theory] // rootDir, includePattern, expectedStem + [InlineData(@"root/dir1", @"*.1", @"test.1")] + [InlineData(@"root/dir1", @"**/*.1", @"test.1")] + [InlineData(@"root", @"dir1/*.1", @"test.1")] + [InlineData(@"root", @"dir1/**/*.1", @"test.1")] + [InlineData(@"root", @"**/dir1/*.1", @"dir1/test.1")] + [InlineData(@"root", @"**/dir1/**/*.1", @"dir1/test.1")] + [InlineData(@"root", @"**/*.1", @"dir1/test.1")] + public void StemIncludesAllSegmentsFromPatternStartingAtWildcard_OneDirectoryDeep(string root, string includePattern, string expectedStem) + { + var fileToFind = @"root/dir1/test.1"; + + var matcher = new Matcher(); + matcher.AddInclude(includePattern); + + var results = matcher.Match(root, new[] { fileToFind }); + var actualStem = results.Files.Select(file => file.Stem).SingleOrDefault(); + + Assert.Equal(expectedStem, actualStem); + + // Also test all scenarios with the `./` current directory prefix + matcher = new Matcher(); + matcher.AddInclude("./" + includePattern); + + results = matcher.Match(root, new[] { fileToFind }); + actualStem = results.Files.Select(file => file.Stem).SingleOrDefault(); + + Assert.Equal(expectedStem, actualStem); + } + + [Theory] // rootDir, includePattern, expectedStem + [InlineData(@"root/dir1/dir2", @"*.2", @"test.2")] + [InlineData(@"root/dir1/dir2", @"**/*.2", @"test.2")] + [InlineData(@"root/dir1", @"dir2/*.2", @"test.2")] + [InlineData(@"root/dir1", @"dir2/**/*.2", @"test.2")] + [InlineData(@"root/dir1", @"**/dir2/*.2", @"dir2/test.2")] + [InlineData(@"root/dir1", @"**/dir2/**/*.2", @"dir2/test.2")] + [InlineData(@"root/dir1", @"**/*.2", @"dir2/test.2")] + [InlineData(@"root", @"dir1/dir2/*.2", @"test.2")] + [InlineData(@"root", @"dir1/dir2/**/*.2", @"test.2")] + [InlineData(@"root", @"**/dir1/dir2/**/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"**/dir1/**/dir2/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"**/dir1/**/dir2/**/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"dir1/**/*.2", @"dir2/test.2")] + [InlineData(@"root", @"**/dir1/**/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"**/dir2/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"**/dir2/**/*.2", @"dir1/dir2/test.2")] + [InlineData(@"root", @"**/*.2", @"dir1/dir2/test.2")] + public void StemIncludesAllSegmentsFromPatternStartingAtWildcard_TwoDirectoriesDeep(string root, string includePattern, string expectedStem) + { + var fileToFind = @"root/dir1/dir2/test.2"; + + var matcher = new Matcher(); + matcher.AddInclude(includePattern); + + var results = matcher.Match(root, new[] { fileToFind }); + var actualStem = results.Files.Select(file => file.Stem).SingleOrDefault(); + + Assert.Equal(expectedStem, actualStem); + + // Also test all scenarios with the `./` current directory prefix + matcher = new Matcher(); + matcher.AddInclude("./" + includePattern); + + results = matcher.Match(root, new[] { fileToFind }); + actualStem = results.Files.Select(file => file.Stem).SingleOrDefault(); + + Assert.Equal(expectedStem, actualStem); + } + private List GetFileList() { return new List { + "root/test.0", + "root/dir1/test.1", + "root/dir1/dir2/test.2", "src/project/source1.cs", "src/project/sub/source2.cs", "src/project/sub/source3.cs",