Skip to content

Commit 2ad3fd6

Browse files
mostynbvbreuss
andauthored
fix: MockFileSystem's File.WriteAllText, File.ReadAllText, Directory.GetFiles, Directory.Create fail with \\?\C:\foo style paths (#1305)
* Test that MockDirectory.CreateDirectory supports windows extended-length paths Windows paths can have a `\\?\` prefix, the '?' character is not always invalid. Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry Refers to #1304 * Make MockDirectory_CreateDirectory_ShouldSupportExtendedLengthPaths pass Fixes #1304 * Also test that MockDirectory.CreateDirectory with an extended path returns a DirectoryInfo with an extended path * Make MockFileSystem's File.WriteAllText, File.ReadAllText and Directory.GetFiles support extended length paths too * Refactor a little --------- Co-authored-by: Valentin Breuß <[email protected]>
1 parent 021976c commit 2ad3fd6

File tree

2 files changed

+41
-3
lines changed

2 files changed

+41
-3
lines changed

src/TestableIO.System.IO.Abstractions.TestingHelpers/PathVerifier.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Linq;
1+
using System;
2+
using System.Linq;
23

34
namespace System.IO.Abstractions.TestingHelpers;
45

@@ -15,6 +16,10 @@ public class PathVerifier
1516
private static readonly char[] AdditionalInvalidPathChars = { '*', '?' };
1617
private readonly IMockFileDataAccessor _mockFileDataAccessor;
1718

19+
// Windows supports extended-length paths with a `\\?\` prefix, to work around low path length limits.
20+
// Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
21+
private const string WINDOWS_EXTENDED_LENGTH_PATH_PREFIX = @"\\?\";
22+
1823
/// <summary>
1924
/// Creates a new verifier instance.
2025
/// </summary>
@@ -64,6 +69,12 @@ public void IsLegalAbsoluteOrRelative(string path, string paramName)
6469

6570
private static bool IsValidUseOfVolumeSeparatorChar(string path)
6671
{
72+
if (XFS.IsWindowsPlatform() && path.StartsWith(WINDOWS_EXTENDED_LENGTH_PATH_PREFIX))
73+
{
74+
// Skip over the `\\?\` prefix if there is one.
75+
path = path.Substring(WINDOWS_EXTENDED_LENGTH_PATH_PREFIX.Length);
76+
}
77+
6778
var lastVolSepIndex = path.LastIndexOf(Path.VolumeSeparatorChar);
6879
return lastVolSepIndex == -1 || lastVolSepIndex == 1 && char.IsLetter(path[0]);
6980
}
@@ -97,6 +108,14 @@ public bool HasIllegalCharacters(string path, bool checkAdditional)
97108

98109
if (checkAdditional)
99110
{
111+
// AdditionalInvalidPathChars includes '?', but this character is allowed in extended-length
112+
// windows path prefixes (`\\?\`). If we're dealing with such a path, check for invalid
113+
// characters after the prefix.
114+
if (XFS.IsWindowsPlatform() && path.StartsWith(WINDOWS_EXTENDED_LENGTH_PATH_PREFIX))
115+
{
116+
path = path.Substring(WINDOWS_EXTENDED_LENGTH_PATH_PREFIX.Length);
117+
}
118+
100119
return path.IndexOfAny(invalidPathChars.Concat(AdditionalInvalidPathChars).ToArray()) >= 0;
101120
}
102121

@@ -164,4 +183,4 @@ public bool TryNormalizeDriveName(string name, out string result)
164183
result = name;
165184
return true;
166185
}
167-
}
186+
}

tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryTests.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,25 @@ public async Task MockDirectory_CreateDirectory_ShouldThrowIfIllegalCharacterInP
747747
await That(createDelegate).Throws<ArgumentException>();
748748
}
749749

750+
[Test]
751+
[WindowsOnly(WindowsSpecifics.UNCPaths)]
752+
public async Task MockDirectory_CreateDirectory_ShouldSupportExtendedLengthPaths()
753+
{
754+
// Arrange
755+
var fileSystem = new MockFileSystem();
756+
757+
// Act
758+
var directoryInfo = fileSystem.Directory.CreateDirectory(XFS.Path(@"\\?\c:\bar"));
759+
fileSystem.File.WriteAllText(@"\\?\c:\bar\grok.txt", "hello world\n");
760+
761+
// Assert
762+
await That(fileSystem.Directory.Exists(XFS.Path(@"\\?\c:\bar"))).IsTrue();
763+
await That(directoryInfo.FullName).IsEqualTo(@"\\?\c:\bar");
764+
await That(fileSystem.File.ReadAllText(@"\\?\c:\bar\grok.txt")).IsEqualTo("hello world\n");
765+
await That(fileSystem.Directory.GetFiles(@"\\?\c:\bar")).HasSingle()
766+
.Which.IsEqualTo(@"\\?\c:\bar\grok.txt");
767+
}
768+
750769
// Issue #210
751770
[Test]
752771
public async Task MockDirectory_CreateDirectory_ShouldIgnoreExistingDirectoryRegardlessOfTrailingSlash()
@@ -2219,4 +2238,4 @@ public static void MockDirectory_Move_ShouldNotThrowException_InWindows_When_Sou
22192238
// Act & Assert
22202239
Assert.DoesNotThrow(() => mockFs.Directory.Move(src, dest));
22212240
}
2222-
}
2241+
}

0 commit comments

Comments
 (0)