Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
<ItemGroup>
<PackageVersion Include="AutoFixture.AutoNSubstitute" Version="5.0.0-preview0012"/>
<PackageVersion Include="AutoFixture.Xunit3" Version="5.0.0-preview0012"/>
<PackageVersion Include="aweXpect" Version="1.6.0"/>
<PackageVersion Include="aweXpect.Testably" Version="0.8.0"/>
<PackageVersion Include="aweXpect" Version="2.18.0"/>
<PackageVersion Include="aweXpect.Testably" Version="0.10.0"/>
<PackageVersion Include="FluentAssertions" Version="7.2.0"/>
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0"/>
<PackageVersion Include="xunit.v3" Version="2.0.3"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ private IEnumerable<string> EnumerateInternal(FileSystemTypes fileSystemTypes,
{
StorageExtensions.AdjustedLocation adjustedLocation = _fileSystem.Storage
.AdjustLocationFromSearchPattern(_fileSystem,
path.EnsureValidFormat(_fileSystem),
path,
searchPattern);
return _fileSystem.Storage.EnumerateLocations(
adjustedLocation.Location,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ internal static string GetSubdirectoryPath(this MockFileSystem fileSystem,
return friendlyName;
}

if (givenPath.IsNtDeviceAlias(fileSystem.Execute))
{
return givenPath.Substring(0, 4) + fullFilePath;
}

return fullFilePath;
}

Expand Down
41 changes: 40 additions & 1 deletion Source/Testably.Abstractions.Testing/Helpers/PathHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace Testably.Abstractions.Testing.Helpers;

Expand All @@ -21,6 +22,7 @@ internal static string EnsureValidFormat(
string? paramName = null,
bool? includeIsEmptyCheck = null)
{
path = RemoveNtDeviceAliasPrefix(path, fileSystem.Execute);
CheckPathArgument(fileSystem.Execute, path, paramName ?? nameof(path),
includeIsEmptyCheck ?? fileSystem.Execute.IsWindows);
CheckPathCharacters(path, fileSystem, paramName ?? nameof(path), null);
Expand Down Expand Up @@ -96,6 +98,21 @@ internal static bool IsEffectivelyEmpty(this string? path, MockFileSystem fileSy
return false;
}

/// <summary>
/// Checks if the path is a Windows NT device alias, meaning it starts with <c>\\?\</c>, <c>\??\</c> or <c>\\.\</c>.
/// </summary>
internal static bool IsNtDeviceAlias(this string path, Execute execute)
{
if (execute.IsWindows && path.StartsWith('\\'))
{
string[] specialPaths = [@"\\?\", @"\??\", @"\\.\",];
return specialPaths.Any(specialPath
=> path.StartsWith(specialPath, StringComparison.Ordinal));
}

return false;
}

internal static bool IsUncPath([NotNullWhen(true)] this string? path, MockFileSystem fileSystem)
{
if (path == null)
Expand All @@ -113,15 +130,37 @@ internal static bool IsUncPath([NotNullWhen(true)] this string? path, MockFileSy
StringComparison.OrdinalIgnoreCase);
}


return path.StartsWith(
new string(fileSystem.Execute.Path.DirectorySeparatorChar, 2),
StringComparison.OrdinalIgnoreCase);
}

/// <summary>
/// Removes the Windows NT device alias prefix from <paramref name="path" />, if it has any.
/// </summary>
/// <remarks>
/// A Windows NT device alias starts with <c>\\?\</c>, <c>\??\</c> or <c>\\.\</c>.
/// </remarks>
[return: NotNullIfNotNull(nameof(path))]
internal static string? RemoveNtDeviceAliasPrefix(this string? path, Execute execute)
{
if (path is null)
{
return null;
}

if (path.IsNtDeviceAlias(execute))
{
return path.Substring(4);
}

return path;
}

internal static void ThrowCommonExceptionsIfPathToTargetIsInvalid(
[NotNull] this string? pathToTarget, MockFileSystem fileSystem)
{
pathToTarget = RemoveNtDeviceAliasPrefix(pathToTarget, fileSystem.Execute);
CheckPathArgument(fileSystem.Execute, pathToTarget, nameof(pathToTarget), false);
CheckPathCharacters(pathToTarget, fileSystem, nameof(pathToTarget), -2147024713);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ public override string ToString()

private static string NormalizeKey(MockFileSystem fileSystem, string fullPath)
{
fullPath = fullPath.RemoveNtDeviceAliasPrefix(fileSystem.Execute);
#if FEATURE_PATH_ADVANCED
return fileSystem.Execute.Path.TrimEndingDirectorySeparator(fullPath);
#else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,8 @@ public IEnumerable<IStorageLocation> EnumerateLocations(
}
}

driveName = driveName.RemoveNtDeviceAliasPrefix(_fileSystem.Execute);

DriveInfoMock drive = DriveInfoMock.New(driveName, _fileSystem);
return _drives.GetValueOrDefault(drive.GetName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public static AdjustedLocation AdjustLocationFromSearchPattern(
throw ExceptionFactory.SearchPatternCannotContainTwoDots();
}

IStorageLocation location = storage.GetLocation(path);
string givenPath = location.FriendlyName;
IStorageLocation location = storage.GetLocation(path.EnsureValidFormat(fileSystem));
string givenPath = path.StartsWith('\\') ? path : location.FriendlyName;
if (searchPattern.StartsWith("..", StringComparison.Ordinal))
{
Stack<string> parentDirectories = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ public void CreateDirectory_ReadOnlyParent_ShouldStillCreateDirectoryUnderWindow
}
}

[Fact]
public async Task CreateDirectory_ShouldSupportExtendedLengthPaths()
{
Skip.If(!Test.RunsOnWindows);

FileSystem.DirectoryInfo.New(@"\\?\c:\bar").Create();

await That(FileSystem.Directory.Exists(@"\\?\c:\bar")).IsTrue();
}

[Theory]
[AutoData]
public void CreateDirectory_FileWithSameNameAlreadyExists_ShouldThrowIOException(string name)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if FEATURE_FILESYSTEM_ENUMERATION_OPTIONS
using Testably.Abstractions.Testing.FileSystem;
#endif

namespace Testably.Abstractions.Tests.FileSystem.Directory;

Expand Down Expand Up @@ -182,6 +184,38 @@ public void EnumerateDirectories_SearchPattern_WithFileExtension_ShouldReturnExp
}
}

[Fact]
public async Task EnumerateDirectories_ShouldSupportExtendedLengthPaths1()
{
Skip.If(!Test.RunsOnWindows);

FileSystem.Directory.CreateDirectory(@"\\?\c:\bar");
FileSystem.File.WriteAllText(@"\\?\c:\bar\foo1.txt", "foo1");
FileSystem.File.WriteAllText(@"\\?\c:\bar\foo2.txt", "foo2");

IEnumerable<string> result = FileSystem.Directory.EnumerateFiles(@"c:\bar");

await That(result)
.IsEqualTo([@"c:\bar\foo1.txt", @"c:\bar\foo2.txt"])
.InAnyOrder();
}

[Fact]
public async Task EnumerateDirectories_ShouldSupportExtendedLengthPaths2()
{
Skip.If(!Test.RunsOnWindows);

FileSystem.Directory.CreateDirectory(@"\\?\c:\bar");
FileSystem.File.WriteAllText(@"\\?\c:\bar\foo1.txt", "foo1");
FileSystem.File.WriteAllText(@"\\?\c:\bar\foo2.txt", "foo2");

IEnumerable<string> result = FileSystem.Directory.EnumerateFiles(@"\\?\c:\bar");

await That(result)
.IsEqualTo([@"\\?\c:\bar\foo1.txt", @"\\?\c:\bar\foo2.txt"])
.InAnyOrder();
}

#if FEATURE_FILESYSTEM_ENUMERATION_OPTIONS
[Theory]
[AutoData]
Expand Down
Loading