// Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Linq; using Microsoft.DotNet.ProjectModel.FileSystemGlobbing.Tests.TestUtility; using Xunit; namespace Microsoft.DotNet.ProjectModel.FileSystemGlobbing.Tests { public class PatternMatchingTests { [Fact] public void EmptyCollectionWhenNoFilesPresent() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("alpha.txt") .Execute(); scenario.AssertExact(); } [Fact] public void MatchingFileIsFound() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("alpha.txt") .Files("alpha.txt") .Execute(); scenario.AssertExact("alpha.txt"); } [Fact] public void MismatchedFileIsIgnored() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("alpha.txt") .Files("omega.txt") .Execute(); scenario.AssertExact(); } [Fact] public void FolderNamesAreTraversed() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("beta/alpha.txt") .Files("beta/alpha.txt") .Execute(); scenario.AssertExact("beta/alpha.txt"); } [Theory] [InlineData(@"beta/alpha.txt", @"beta/alpha.txt")] [InlineData(@"beta\alpha.txt", @"beta/alpha.txt")] [InlineData(@"beta/alpha.txt", @"beta\alpha.txt")] [InlineData(@"beta\alpha.txt", @"beta\alpha.txt")] [InlineData(@"\beta\alpha.txt", @"beta\alpha.txt")] public void SlashPolarityIsIgnored(string includePattern, string filePath) { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include(includePattern) .Files("one/two.txt", filePath, "three/four.txt") .Execute(); scenario.AssertExact("beta/alpha.txt"); } [Theory] [InlineData(@"*.txt", new[] { "alpha.txt", "beta.txt" })] [InlineData(@"alpha.*", new[] { "alpha.txt" })] [InlineData(@"*.*", new[] { "alpha.txt", "beta.txt", "gamma.dat" })] [InlineData(@"*", new[] { "alpha.txt", "beta.txt", "gamma.dat" })] [InlineData(@"*et*", new[] { "beta.txt" })] [InlineData(@"b*et*t", new[] { "beta.txt" })] [InlineData(@"b*et*x", new string[0])] public void PatternMatchingWorks(string includePattern, string[] matchesExpected) { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include(includePattern) .Files("alpha.txt", "beta.txt", "gamma.dat") .Execute(); scenario.AssertExact(matchesExpected); } [Theory] [InlineData(@"1234*5678", new[] { "12345678" })] [InlineData(@"12345*5678", new string[0])] [InlineData(@"12*3456*78", new[] { "12345678" })] [InlineData(@"12*23*", new string[0])] [InlineData(@"*67*78", new string[0])] [InlineData(@"*45*56", new string[0])] public void PatternBeginAndEndCantOverlap(string includePattern, string[] matchesExpected) { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include(includePattern) .Files("12345678") .Execute(); scenario.AssertExact(matchesExpected); } [Theory] [InlineData(@"*mm*/*", new[] { "gamma/hello.txt" })] [InlineData(@"/*mm*/*", new[] { "gamma/hello.txt" })] [InlineData(@"*alpha*/*", new[] { "alpha/hello.txt" })] [InlineData(@"/*alpha*/*", new[] { "alpha/hello.txt" })] [InlineData(@"*/*", new[] { "alpha/hello.txt", "beta/hello.txt", "gamma/hello.txt" })] [InlineData(@"/*/*", new[] { "alpha/hello.txt", "beta/hello.txt", "gamma/hello.txt" })] [InlineData(@"*.*/*", new[] { "alpha/hello.txt", "beta/hello.txt", "gamma/hello.txt" })] [InlineData(@"/*.*/*", new[] { "alpha/hello.txt", "beta/hello.txt", "gamma/hello.txt" })] public void PatternMatchingWorksInFolders(string includePattern, string[] matchesExpected) { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include(includePattern) .Files("alpha/hello.txt", "beta/hello.txt", "gamma/hello.txt") .Execute(); scenario.AssertExact(matchesExpected); } [Theory] [InlineData(@"", new string[] { })] [InlineData(@"./", new string[] { "alpha/hello.txt", "beta/hello.txt", "gamma/hello.txt" })] [InlineData(@"./alpha/hello.txt", new string[] { "alpha/hello.txt" })] [InlineData(@"./**/hello.txt", new string[] { "alpha/hello.txt", "beta/hello.txt", "gamma/hello.txt" })] [InlineData(@"././**/hello.txt", new string[] { "alpha/hello.txt", "beta/hello.txt", "gamma/hello.txt" })] [InlineData(@"././**/./hello.txt", new string[] { "alpha/hello.txt", "beta/hello.txt", "gamma/hello.txt" })] [InlineData(@"././**/./**/hello.txt", new string[] { "alpha/hello.txt", "beta/hello.txt", "gamma/hello.txt" })] [InlineData(@"./*mm*/hello.txt", new string[] { "gamma/hello.txt" })] [InlineData(@"./*mm*/*", new string[] { "gamma/hello.txt" })] public void PatternMatchingCurrent(string includePattern, string[] matchesExpected) { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include(includePattern) .Files("alpha/hello.txt", "beta/hello.txt", "gamma/hello.txt") .Execute(); scenario.AssertExact(matchesExpected); } [Fact] public void StarDotStarIsSameAsStar() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("*.*") .Files("alpha.txt", "alpha.", ".txt", ".", "alpha", "txt") .Execute(); scenario.AssertExact("alpha.txt", "alpha.", ".txt", ".", "alpha", "txt"); } [Fact] public void IncompletePatternsDoNotInclude() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("*/*.txt") .Files("one/x.txt", "two/x.txt", "x.txt") .Execute(); scenario.AssertExact("one/x.txt", "two/x.txt"); } [Fact] public void IncompletePatternsDoNotExclude() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("*/*.txt") .Exclude("one/hello.txt") .Files("one/x.txt", "two/x.txt") .Execute(); scenario.AssertExact("one/x.txt", "two/x.txt"); } [Fact] public void TrailingRecursiveWildcardMatchesAllFiles() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("one/**") .Files("one/x.txt", "two/x.txt", "one/x/y.txt") .Execute(); scenario.AssertExact("one/x.txt", "one/x/y.txt"); } [Fact] public void LeadingRecursiveWildcardMatchesAllLeadingPaths() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("**/*.cs") .Files("one/x.cs", "two/x.cs", "one/two/x.cs", "x.cs") .Files("one/x.txt", "two/x.txt", "one/two/x.txt", "x.txt") .Execute(); scenario.AssertExact("one/x.cs", "two/x.cs", "one/two/x.cs", "x.cs"); } [Fact] public void InnerRecursiveWildcardMuseStartWithAndEndWith() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("one/**/*.cs") .Files("one/x.cs", "two/x.cs", "one/two/x.cs", "x.cs") .Files("one/x.txt", "two/x.txt", "one/two/x.txt", "x.txt") .Execute(); scenario.AssertExact("one/x.cs", "one/two/x.cs"); } [Fact] public void ExcludeMayEndInDirectoryName() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("*.cs", "*/*.cs", "*/*/*.cs") .Exclude("bin", "one/two") .Files("one/x.cs", "two/x.cs", "one/two/x.cs", "x.cs", "bin/x.cs", "bin/two/x.cs") .Execute(); scenario.AssertExact("one/x.cs", "two/x.cs", "x.cs"); } [Fact] public void RecursiveWildcardSurroundingContainsWith() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("**/x/**") .Files("x/1", "1/x/2", "1/x", "x", "1", "1/2") .Execute(); scenario.AssertExact("x/1", "1/x/2"); } [Fact] public void SequentialFoldersMayBeRequired() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("a/b/**/1/2/**/2/3/**") .Files("1/2/2/3/x", "1/2/3/y", "a/1/2/4/2/3/b", "a/2/3/1/2/b") .Files("a/b/1/2/2/3/x", "a/b/1/2/3/y", "a/b/a/1/2/4/2/3/b", "a/b/a/2/3/1/2/b") .Execute(); scenario.AssertExact("a/b/1/2/2/3/x", "a/b/a/1/2/4/2/3/b"); } [Fact] public void RecursiveAloneIncludesEverything() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("**") .Files("1/2/2/3/x", "1/2/3/y") .Execute(); scenario.AssertExact("1/2/2/3/x", "1/2/3/y"); } [Fact] public void ExcludeCanHaveSurroundingRecursiveWildcards() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("**") .Exclude("**/x/**") .Files("x/1", "1/x/2", "1/x", "x", "1", "1/2") .Execute(); scenario.AssertExact("1/x", "x", "1", "1/2"); } [Fact] public void LeadingDotDotCanComeThroughPattern() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("*.cs") .Include("../2/*.cs") .Files("1/x.cs", "1/x.txt", "2/x.cs", "2/x.txt") .SubDirectory("1") .Execute(); scenario.AssertExact("x.cs", "../2/x.cs"); } [Fact] public void LeadingDotDotWithRecursiveCanComeThroughPattern() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("*.cs") .Include("../2/**/*.cs") .Files("1/x.cs", "1/x.txt", "2/x.cs", "2/x.txt", "2/3/x.cs", "2/3/4/z.cs", "2/3/x.txt") .SubDirectory("1") .Execute(); scenario.AssertExact("x.cs", "../2/x.cs", "../2/3/x.cs", "../2/3/4/z.cs"); } [Fact] public void ExcludeFolderRecursively() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("*.*") .Include("../sibling/**/*.*") .Exclude("../sibling/exc/**/*.*") .Exclude("../sibling/inc/2.txt") .Files("main/1.txt", "main/2.txt", "sibling/1.txt", "sibling/inc/1.txt", "sibling/inc/2.txt", "sibling/exc/1.txt", "sibling/exc/2.txt") .SubDirectory("main") .Execute(); scenario.AssertExact("1.txt", "2.txt", "../sibling/1.txt", "../sibling/inc/1.txt"); } [Fact] public void ExcludeFolderByName() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("*.*") .Include("../sibling/**/*.*") .Exclude("../sibling/exc/") .Exclude("../sibling/inc/2.txt") .Files("main/1.txt", "main/2.txt", "sibling/1.txt", "sibling/inc/1.txt", "sibling/inc/2.txt", "sibling/exc/1.txt", "sibling/exc/2.txt") .SubDirectory("main") .Execute(); scenario.AssertExact("1.txt", "2.txt", "../sibling/1.txt", "../sibling/inc/1.txt"); } [Fact] public void MultipleRecursiveWildcardStemMatch() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("sub/**/bar/**/*.txt") .Files("root.txt", "sub/one.txt", "sub/two.txt", "sub/sub2/bar/baz/three.txt", "sub/sub3/sub4/bar/three.txt") .Execute(); // Check the stem of the matched items Assert.Equal(new[] { new FilePatternMatch(path: "sub/sub2/bar/baz/three.txt", stem: "sub2/bar/baz/three.txt"), new FilePatternMatch(path: "sub/sub3/sub4/bar/three.txt", stem: "sub3/sub4/bar/three.txt") }, scenario.Result.Files.ToArray()); } [Fact] public void RecursiveWildcardStemMatch() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("sub/**/*.txt") .Files("root.txt", "sub/one.txt", "sub/two.txt", "sub/sub2/three.txt") .Execute(); // Check the stem of the matched items Assert.Equal(new[] { new FilePatternMatch(path: "sub/one.txt", stem: "one.txt"), new FilePatternMatch(path: "sub/two.txt", stem: "two.txt"), new FilePatternMatch(path: "sub/sub2/three.txt", stem: "sub2/three.txt") }, scenario.Result.Files.ToArray()); } [Fact] public void WildcardMidSegmentMatch() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("sub/w*.txt") .Files("root.txt", "sub/woah.txt", "sub/wow.txt", "sub/blah.txt") .Execute(); // Check the stem of the matched items Assert.Equal(new[] { new FilePatternMatch(path: "sub/woah.txt", stem: "woah.txt"), new FilePatternMatch(path: "sub/wow.txt", stem: "wow.txt") }, scenario.Result.Files.ToArray()); } [Fact] public void StemMatchOnExactFile() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("sub/sub/three.txt") .Files("root.txt", "sub/one.txt", "sub/two.txt", "sub/sub/three.txt") .Execute(); // Check the stem of the matched items Assert.Equal(new[] { new FilePatternMatch(path: "sub/sub/three.txt", stem: "three.txt"), }, scenario.Result.Files.ToArray()); } [Fact] public void SimpleStemMatching() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("sub/*") .Files("root.txt", "sub/one.txt", "sub/two.txt", "sub/sub/three.txt") .Execute(); // Check the stem of the matched items Assert.Equal(new[] { new FilePatternMatch(path: "sub/one.txt", stem: "one.txt"), new FilePatternMatch(path: "sub/two.txt", stem: "two.txt") }, scenario.Result.Files.ToArray()); } [Fact] public void StemMatchingWithFileExtension() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("sub/*.txt") .Files("root.txt", "sub/one.txt", "sub/two.txt", "sub/three.dat") .Execute(); // Check the stem of the matched items Assert.Equal(new[] { new FilePatternMatch(path: "sub/one.txt", stem: "one.txt"), new FilePatternMatch(path: "sub/two.txt", stem: "two.txt") }, scenario.Result.Files.ToArray()); } [Fact] public void StemMatchingWithParentDir() { var matcher = new Matcher(); var scenario = new FileSystemGlobbingTestContext(@"c:\files\", matcher) .Include("../files/sub/*.txt") .Files("root.txt", "sub/one.txt", "sub/two.txt", "sub/three.dat") .Execute(); // Check the stem of the matched items Assert.Equal(new[] { new FilePatternMatch(path: "../files/sub/one.txt", stem: "one.txt"), new FilePatternMatch(path: "../files/sub/two.txt", stem: "two.txt") }, scenario.Result.Files.ToArray()); } // exclude: **/.*/** // exclude: node_modules/* // exclude: **/.cs } }