FileSystemGlobbing: Allow rootDir paths ending with separator to match files correctl...
authorDavid CantĂș <dacantu@microsoft.com>
Tue, 30 Mar 2021 16:45:10 +0000 (09:45 -0700)
committerGitHub <noreply@github.com>
Tue, 30 Mar 2021 16:45:10 +0000 (09:45 -0700)
* Allow rootDir paths ending with separator to match files correctly

* Avoid running tests with windows-like absolute paths on non-windows platforms

* Address test suggestion

src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/InMemoryDirectoryInfo.cs
src/libraries/Microsoft.Extensions.FileSystemGlobbing/tests/FunctionalTests.cs

index d94f198..55688a8 100644 (file)
@@ -119,13 +119,11 @@ namespace Microsoft.Extensions.FileSystemGlobbing
 
         private bool IsRootDirectory(string rootDir, string filePath)
         {
-            if (!filePath.StartsWith(rootDir, StringComparison.Ordinal) ||
-                filePath.IndexOf(Path.DirectorySeparatorChar, rootDir.Length) != rootDir.Length)
-            {
-                return false;
-            }
+            int rootDirLength = rootDir.Length;
 
-            return true;
+            return filePath.StartsWith(rootDir, StringComparison.Ordinal) &&
+                (rootDir[rootDirLength - 1] == Path.DirectorySeparatorChar ||
+                filePath.IndexOf(Path.DirectorySeparatorChar, rootDirLength) == rootDirLength);
         }
 
         /// <inheritdoc />
index ce56c09..602047b 100644 (file)
@@ -635,9 +635,118 @@ namespace Microsoft.Extensions.FileSystemGlobbing.Tests
             Assert.Equal(expectedStem, actualStem);
         }
 
-        private List<string> GetFileList()
+        [Theory]
+        [InlineData("/", '/')]
+        public void RootDir_IsPathRoot_WithInMemory_AllOS(string rootDir, char separator)
+        {
+            RootDir_IsPathRoot_WithInMemory(rootDir, separator);
+        }
+
+        [Theory]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        [InlineData("C:\\", '\\')]
+        [InlineData("C:/", '/')]
+        public void RootDir_IsPathRoot_WithInMemory_WindowsOnly(string rootDir, char separator)
         {
-            return new List<string>
+            RootDir_IsPathRoot_WithInMemory(rootDir, separator);
+        }
+
+        private static void RootDir_IsPathRoot_WithInMemory(string rootDir, char separator)
+        {
+            var matcher = new Matcher();
+            matcher.AddInclude($"**{separator}*.cs");
+
+            IEnumerable<string> files = GetFileList(rootDir, separator);
+            PatternMatchingResult results = matcher.Match(rootDir, files);
+
+            IEnumerable<string> actual = results.Files.Select(match => match.Path);
+            IEnumerable<string> expected = new string[]
+            {
+                "src/project/source1.cs",
+                "src/project/sub/source2.cs",
+                "src/project/sub/source3.cs",
+                "src/project/sub2/source4.cs",
+                "src/project/sub2/source5.cs",
+                "src/project/compiler/preprocess/preprocess-source1.cs",
+                "src/project/compiler/preprocess/sub/preprocess-source2.cs",
+                "src/project/compiler/preprocess/sub/sub/preprocess-source3.cs",
+                "src/project/compiler/shared/shared1.cs",
+                "src/project/compiler/shared/sub/shared2.cs",
+                "src/project/compiler/shared/sub/sub/sharedsub.cs",
+                "src/project2/source1.cs",
+                "src/project2/sub/source2.cs",
+                "src/project2/sub/source3.cs",
+                "src/project2/sub2/source4.cs",
+                "src/project2/sub2/source5.cs",
+                "src/project2/compiler/preprocess/preprocess-source1.cs",
+                "src/project2/compiler/preprocess/sub/preprocess-source2.cs",
+                "src/project2/compiler/preprocess/sub/sub/preprocess-source3.cs",
+                "src/project2/compiler/shared/shared1.cs",
+                "src/project2/compiler/shared/sub/shared2.cs",
+                "src/project2/compiler/shared/sub/sub/sharedsub.cs",
+                "lib/source6.cs",
+                "lib/sub3/source7.cs",
+                "lib/sub4/source8.cs",
+            };
+
+            Assert.Equal(
+                expected.OrderBy(e => e),
+                actual.OrderBy(e => e),
+                StringComparer.OrdinalIgnoreCase);
+        }
+
+        [Theory]
+        [InlineData("/src/project", '/')]
+        [InlineData("/src/project/", '/')]
+        public void RootDir_IsAbsolutePath_WithInMemory_AllOS(string rootDir, char separator)
+        {
+            RootDir_IsAbsolutePath_WithInMemory(rootDir, separator);
+        }
+        
+        [Theory]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        [InlineData("C:\\src\\project", '\\')]
+        [InlineData("C:\\src\\project\\", '\\')]
+        [InlineData("C:/src/project", '/')]
+        [InlineData("C:/src/project/", '/')]
+        public void RootDir_IsAbsolutePath_WithInMemory_WindowsOnly(string rootDir, char separator)
+        {
+            RootDir_IsAbsolutePath_WithInMemory(rootDir, separator);
+        }
+
+        private static void RootDir_IsAbsolutePath_WithInMemory(string rootDir, char separator)
+        {
+            var matcher = new Matcher();
+            matcher.AddInclude($"**{separator}*.cs");
+
+            IEnumerable<string> files = GetFileList(Path.GetPathRoot(rootDir), separator);
+            PatternMatchingResult results = matcher.Match(rootDir, files);
+
+            IEnumerable<string> actual = results.Files.Select(match => match.Path);
+            IEnumerable<string> expected = new string[]
+            {
+                "source1.cs",
+                "sub/source2.cs",
+                "sub/source3.cs",
+                "sub2/source4.cs",
+                "sub2/source5.cs",
+                "compiler/preprocess/preprocess-source1.cs",
+                "compiler/preprocess/sub/preprocess-source2.cs",
+                "compiler/preprocess/sub/sub/preprocess-source3.cs",
+                "compiler/shared/shared1.cs",
+                "compiler/shared/sub/shared2.cs",
+                "compiler/shared/sub/sub/sharedsub.cs"
+            };
+
+            Assert.Equal(
+                expected.OrderBy(e => e),
+                actual.OrderBy(e => e),
+                StringComparer.OrdinalIgnoreCase);
+        }
+
+        private static IEnumerable<string> GetFileList(string rootDir = "", char directorySeparator = '/')
+        {
+            var files = new List<string>
             {
                 "root/test.0",
                 "root/dir1/test.1",
@@ -693,6 +802,8 @@ namespace Microsoft.Extensions.FileSystemGlobbing.Tests
                 ".hidden/file1.hid",
                 ".hidden/sub/file2.hid"
             };
+
+            return files.Select(x => (rootDir + x).Replace('/', directorySeparator));
         }
 
         private DisposableFileSystem CreateContext()
@@ -713,5 +824,44 @@ namespace Microsoft.Extensions.FileSystemGlobbing.Tests
 
             AssertExtensions.CollectionEqual(expected, actual, StringComparer.OrdinalIgnoreCase);
         }
+
+        [Fact] // https://github.com/dotnet/runtime/issues/44767
+        public void VerifyAbsolutePaths_HasMatches()
+        {
+            var fileMatcher = new Matcher();
+            fileMatcher.AddInclude("**/*");
+
+            if (PlatformDetection.IsWindows)
+            {
+                // Windows-like absolute paths are not supported on Unix.
+                string fakeWindowsPath = "C:\\This\\is\\a\\nested\\windows-like\\path\\somefile.cs";
+                Assert.True(fileMatcher.Match(Path.GetPathRoot(fakeWindowsPath), fakeWindowsPath).HasMatches);
+            }
+            
+            // Unix-like absolute paths are treated as relative paths on Windows.
+            string fakeUnixPath = "/This/is/a/nested/unix-like/path/somefile.cs";
+            Assert.True(fileMatcher.Match(Path.GetPathRoot(fakeUnixPath), fakeUnixPath).HasMatches);
+        }
+
+        [Fact] // https://github.com/dotnet/runtime/issues/36415
+        public void VerifyInMemoryDirectoryInfo_IsNotEmpty()
+        {
+            IEnumerable<string> files = new[] { @"pagefile.sys" };
+            InMemoryDirectoryInfo directoryInfo;
+            IEnumerable<FileSystemInfoBase> fileSystemInfos;
+
+            if (PlatformDetection.IsWindows)
+            {
+                directoryInfo = new InMemoryDirectoryInfo(@"C:\", files);
+                fileSystemInfos = directoryInfo.EnumerateFileSystemInfos();
+
+                Assert.Equal(1, fileSystemInfos.Count());
+            }
+
+            directoryInfo = new InMemoryDirectoryInfo("/", files);
+            fileSystemInfos = directoryInfo.EnumerateFileSystemInfos();
+
+            Assert.Equal(1, fileSystemInfos.Count());
+        }
     }
 }