diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/PathVerifier.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/PathVerifier.cs
index b56aa674c..2c426637c 100644
--- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/PathVerifier.cs
+++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/PathVerifier.cs
@@ -1,4 +1,5 @@
-using System.Linq;
+using System;
+using System.Linq;
namespace System.IO.Abstractions.TestingHelpers;
@@ -15,6 +16,10 @@ public class PathVerifier
private static readonly char[] AdditionalInvalidPathChars = { '*', '?' };
private readonly IMockFileDataAccessor _mockFileDataAccessor;
+ // Windows supports extended-length paths with a `\\?\` prefix, to work around low path length limits.
+ // Ref: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
+ private const string WINDOWS_EXTENDED_LENGTH_PATH_PREFIX = @"\\?\";
+
///
/// Creates a new verifier instance.
///
@@ -64,6 +69,12 @@ public void IsLegalAbsoluteOrRelative(string path, string paramName)
private static bool IsValidUseOfVolumeSeparatorChar(string path)
{
+ if (XFS.IsWindowsPlatform() && path.StartsWith(WINDOWS_EXTENDED_LENGTH_PATH_PREFIX))
+ {
+ // Skip over the `\\?\` prefix if there is one.
+ path = path.Substring(WINDOWS_EXTENDED_LENGTH_PATH_PREFIX.Length);
+ }
+
var lastVolSepIndex = path.LastIndexOf(Path.VolumeSeparatorChar);
return lastVolSepIndex == -1 || lastVolSepIndex == 1 && char.IsLetter(path[0]);
}
@@ -97,6 +108,14 @@ public bool HasIllegalCharacters(string path, bool checkAdditional)
if (checkAdditional)
{
+ // AdditionalInvalidPathChars includes '?', but this character is allowed in extended-length
+ // windows path prefixes (`\\?\`). If we're dealing with such a path, check for invalid
+ // characters after the prefix.
+ if (XFS.IsWindowsPlatform() && path.StartsWith(WINDOWS_EXTENDED_LENGTH_PATH_PREFIX))
+ {
+ path = path.Substring(WINDOWS_EXTENDED_LENGTH_PATH_PREFIX.Length);
+ }
+
return path.IndexOfAny(invalidPathChars.Concat(AdditionalInvalidPathChars).ToArray()) >= 0;
}
@@ -164,4 +183,4 @@ public bool TryNormalizeDriveName(string name, out string result)
result = name;
return true;
}
-}
\ No newline at end of file
+}
diff --git a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryTests.cs
index fae8b65a3..79b531edc 100644
--- a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryTests.cs
+++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockDirectoryTests.cs
@@ -747,6 +747,25 @@ public async Task MockDirectory_CreateDirectory_ShouldThrowIfIllegalCharacterInP
await That(createDelegate).Throws();
}
+ [Test]
+ [WindowsOnly(WindowsSpecifics.UNCPaths)]
+ public async Task MockDirectory_CreateDirectory_ShouldSupportExtendedLengthPaths()
+ {
+ // Arrange
+ var fileSystem = new MockFileSystem();
+
+ // Act
+ var directoryInfo = fileSystem.Directory.CreateDirectory(XFS.Path(@"\\?\c:\bar"));
+ fileSystem.File.WriteAllText(@"\\?\c:\bar\grok.txt", "hello world\n");
+
+ // Assert
+ await That(fileSystem.Directory.Exists(XFS.Path(@"\\?\c:\bar"))).IsTrue();
+ await That(directoryInfo.FullName).IsEqualTo(@"\\?\c:\bar");
+ await That(fileSystem.File.ReadAllText(@"\\?\c:\bar\grok.txt")).IsEqualTo("hello world\n");
+ await That(fileSystem.Directory.GetFiles(@"\\?\c:\bar")).HasSingle()
+ .Which.IsEqualTo(@"\\?\c:\bar\grok.txt");
+ }
+
// Issue #210
[Test]
public async Task MockDirectory_CreateDirectory_ShouldIgnoreExistingDirectoryRegardlessOfTrailingSlash()
@@ -2219,4 +2238,4 @@ public static void MockDirectory_Move_ShouldNotThrowException_InWindows_When_Sou
// Act & Assert
Assert.DoesNotThrow(() => mockFs.Directory.Move(src, dest));
}
-}
\ No newline at end of file
+}