Change PathInternal.IsCaseSensitive to a constant (#54340)
authorAleksey Kliger (λgeek) <alklig@microsoft.com>
Wed, 23 Jun 2021 13:44:41 +0000 (09:44 -0400)
committerGitHub <noreply@github.com>
Wed, 23 Jun 2021 13:44:41 +0000 (09:44 -0400)
* Return constants in PathInternal.GetIsCaseSensitive() on mobile platforms

   In particular on Android probing using I/O is slow and contributes to slow app startup.

   Fixes https://github.com/dotnet/runtime/issues/54339

* Implement Path.IsCaseSensitive as PathInternal.IsCaseSensitive

   Also Path.StringComparison => PathInternal.StringComparison

* Add test for PathInternal.IsCaseSensitive

   Move GetIsCaseSensitiveByProbing to FileSystemTest

* Drop PathInternal.s_isCaseSensitive cache field

* Delete Path.IsCaseSensitive and Path.StringComparison

   update callers to use PathInternal.IsCaseSensitive and PathInternal.StringComparison

* Remove catch clause from GetIsCaseSensitiveByProbing

* Mark new test [OuterLoop]

* Apply suggestions from code review

Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
Co-authored-by: Stephen Toub <stoub@microsoft.com>
src/libraries/Common/src/System/IO/PathInternal.CaseSensitivity.cs
src/libraries/System.IO.FileSystem/tests/FileSystemTest.cs
src/libraries/System.IO.FileSystem/tests/Net5CompatTests/System.IO.FileSystem.Net5Compat.Tests.csproj
src/libraries/System.IO.FileSystem/tests/PathInternalTests.cs [new file with mode: 0644]
src/libraries/System.IO.FileSystem/tests/System.IO.FileSystem.Tests.csproj
src/libraries/System.Private.CoreLib/src/System/IO/Path.Unix.cs
src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs
src/libraries/System.Private.CoreLib/src/System/IO/Path.cs
src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs

index 5611e9c..3e34861 100644 (file)
@@ -12,47 +12,27 @@ namespace System.IO
     /// <summary>Contains internal path helpers that are shared between many projects.</summary>
     internal static partial class PathInternal
     {
-        private static readonly bool s_isCaseSensitive = GetIsCaseSensitive();
-
         /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
         internal static StringComparison StringComparison
         {
             get
             {
-                return s_isCaseSensitive ?
+                return IsCaseSensitive ?
                     StringComparison.Ordinal :
                     StringComparison.OrdinalIgnoreCase;
             }
         }
 
         /// <summary>Gets whether the system is case-sensitive.</summary>
-        internal static bool IsCaseSensitive { get { return s_isCaseSensitive; } }
-
-        /// <summary>
-        /// Determines whether the file system is case sensitive.
-        /// </summary>
-        /// <remarks>
-        /// Ideally we'd use something like pathconf with _PC_CASE_SENSITIVE, but that is non-portable,
-        /// not supported on Windows or Linux, etc. For now, this function creates a tmp file with capital letters
-        /// and then tests for its existence with lower-case letters.  This could return invalid results in corner
-        /// cases where, for example, different file systems are mounted with differing sensitivities.
-        /// </remarks>
-        private static bool GetIsCaseSensitive()
+        internal static bool IsCaseSensitive
         {
-            try
-            {
-                string pathWithUpperCase = Path.Combine(Path.GetTempPath(), "CASESENSITIVETEST" + Guid.NewGuid().ToString("N"));
-                using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose))
-                {
-                    string lowerCased = pathWithUpperCase.ToLowerInvariant();
-                    return !File.Exists(lowerCased);
-                }
-            }
-            catch
+            get
             {
-                // In case something goes wrong (e.g. temp pointing to a privilieged directory), we don't
-                // want to fail just because of a casing test, so we assume case-insensitive-but-preserving.
-                return false;
+#if MS_IO_REDIST
+                return false; // Windows is always case-insensitive
+#else
+                return !(OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || OperatingSystem.IsMacCatalyst() || OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() || OperatingSystem.IsWatchOS());
+#endif
             }
         }
     }
index 2ba15d3..d82346f 100644 (file)
@@ -127,5 +127,24 @@ namespace System.IO.Tests
                 Assert.Equal(0, AdminHelpers.RunAsSudo($"umount {readOnlyDirectory}"));
             }
         }
+
+        /// <summary>
+        /// Determines whether the file system is case sensitive by creating a file in the specified folder and observing the result.
+        /// </summary>
+        /// <remarks>
+        /// Ideally we'd use something like pathconf with _PC_CASE_SENSITIVE, but that is non-portable,
+        /// not supported on Windows or Linux, etc. For now, this function creates a tmp file with capital letters
+        /// and then tests for its existence with lower-case letters.  This could return invalid results in corner
+        /// cases where, for example, different file systems are mounted with differing sensitivities.
+        /// </remarks>
+        protected static bool GetIsCaseSensitiveByProbing(string probingDirectory)
+        {
+            string pathWithUpperCase = Path.Combine(probingDirectory, "CASESENSITIVETEST" + Guid.NewGuid().ToString("N"));
+            using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose))
+            {
+                string lowerCased = pathWithUpperCase.ToLowerInvariant();
+                return !File.Exists(lowerCased);
+            }
+        }
     }
 }
index 628bbb4..5866965 100644 (file)
@@ -37,6 +37,8 @@
     <Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
     <Compile Include="$(CommonTestPath)System\IO\PathFeatures.cs" Link="Common\System\IO\PathFeatures.cs" />
     <Content Include="..\DirectoryInfo\test-dir\dummy.txt" Link="test-dir\dummy.txt" />
+    <Compile Include="$(CommonPath)System\IO\PathInternal.CaseSensitivity.cs"
+             Link="Common\System\IO\PathInternal.CaseSensitivity.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
diff --git a/src/libraries/System.IO.FileSystem/tests/PathInternalTests.cs b/src/libraries/System.IO.FileSystem/tests/PathInternalTests.cs
new file mode 100644 (file)
index 0000000..c908601
--- /dev/null
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+namespace System.IO.Tests
+{
+    public class PathInternalTests : FileSystemTest
+    {
+        [Fact]
+        [OuterLoop]
+        public void PathInternalIsCaseSensitiveMatchesProbing()
+        {
+            string probingDirectory = TestDirectory;
+            Assert.Equal(GetIsCaseSensitiveByProbing(probingDirectory), PathInternal.IsCaseSensitive);
+        }
+    }
+}
index a271109..94cec86 100644 (file)
@@ -51,6 +51,7 @@
     <Compile Include="Enumeration\ExampleTests.cs" />
     <Compile Include="Enumeration\RemovedDirectoryTests.cs" />
     <Compile Include="Enumeration\SymbolicLinksTests.cs" />
+    <Compile Include="PathInternalTests.cs" />
     <Compile Include="RandomAccess\Base.cs" />
     <Compile Include="RandomAccess\GetLength.cs" />
     <Compile Include="RandomAccess\Read.cs" />
     <Compile Include="$(CommonTestPath)System\IO\TempFile.cs" Link="Common\System\IO\TempFile.cs" />
     <Compile Include="$(CommonTestPath)System\IO\PathFeatures.cs" Link="Common\System\IO\PathFeatures.cs" />
     <Content Include="DirectoryInfo\test-dir\dummy.txt" Link="test-dir\dummy.txt" />
+    <Compile Include="$(CommonPath)System\IO\PathInternal.CaseSensitivity.cs"
+             Link="Common\System\IO\PathInternal.CaseSensitivity.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="$(CommonTestPath)StreamConformanceTests\StreamConformanceTests.csproj" />
index ecc990b..5a7f0c8 100644 (file)
@@ -129,17 +129,5 @@ namespace System.IO
             return IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString.AsSpan() : ReadOnlySpan<char>.Empty;
         }
 
-        /// <summary>Gets whether the system is case-sensitive.</summary>
-        internal static bool IsCaseSensitive
-        {
-            get
-            {
-                #if TARGET_OSX || TARGET_MACCATALYST || TARGET_IOS || TARGET_TVOS
-                    return false;
-                #else
-                    return true;
-                #endif
-            }
-        }
     }
 }
index 5e64180..5f4b8e1 100644 (file)
@@ -230,9 +230,6 @@ namespace System.IO
             return pathRoot <= 0 ? ReadOnlySpan<char>.Empty : path.Slice(0, pathRoot);
         }
 
-        /// <summary>Gets whether the system is case-sensitive.</summary>
-        internal static bool IsCaseSensitive => false;
-
         /// <summary>
         /// Returns the volume name for dos, UNC and device paths.
         /// </summary>
index 7601e45..104704d 100644 (file)
@@ -860,7 +860,7 @@ namespace System.IO
         /// <exception cref="ArgumentNullException">Thrown if <paramref name="relativeTo"/> or <paramref name="path"/> is <c>null</c> or an empty string.</exception>
         public static string GetRelativePath(string relativeTo, string path)
         {
-            return GetRelativePath(relativeTo, path, StringComparison);
+            return GetRelativePath(relativeTo, path, PathInternal.StringComparison);
         }
 
         private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType)
@@ -957,12 +957,6 @@ namespace System.IO
             return sb.ToString();
         }
 
-        /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
-        internal static StringComparison StringComparison =>
-            IsCaseSensitive ?
-                StringComparison.Ordinal :
-                StringComparison.OrdinalIgnoreCase;
-
         /// <summary>
         /// Trims one trailing directory separator beyond the root of the path.
         /// </summary>
index 6c733ef..2a5a3c1 100644 (file)
@@ -781,7 +781,7 @@ namespace System.Runtime.Loader
             string assemblyPath = Path.Combine(parentDirectory, assemblyName.CultureName!, $"{assemblyName.Name}.dll");
 
             bool exists = System.IO.FileSystem.FileExists(assemblyPath);
-            if (!exists && Path.IsCaseSensitive)
+            if (!exists && PathInternal.IsCaseSensitive)
             {
 #if CORECLR
                 if (AssemblyLoadContext.IsTracingEnabled())