Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
<ItemGroup>
<PackageVersion Include="AutoFixture.AutoNSubstitute" Version="5.0.0-preview0012"/>
<PackageVersion Include="AutoFixture.Xunit3" Version="5.0.0-preview0012"/>
<PackageVersion Include="aweXpect" Version="2.21.0"/>
<PackageVersion Include="aweXpect" Version="2.21.1"/>
<PackageVersion Include="aweXpect.Testably" Version="0.11.0"/>
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageVersion Include="xunit.v3" Version="2.0.3"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,27 +336,14 @@ public void Refresh()
/// <inheritdoc cref="IFileSystemInfo.ResolveLinkTarget(bool)" />
public IFileSystemInfo? ResolveLinkTarget(bool returnFinalTarget)
{
using IDisposable registration = RegisterPathMethod(nameof(ResolveLinkTarget),
returnFinalTarget);
using IDisposable registration = RegisterPathMethod(
nameof(ResolveLinkTarget), returnFinalTarget
);

try
{
IStorageLocation? targetLocation =
_fileSystem.Storage.ResolveLinkTarget(
Location,
returnFinalTarget);
if (targetLocation != null)
{
return New(targetLocation, _fileSystem);
}
IStorageLocation? targetLocation
= _fileSystem.Storage.ResolveLinkTarget(Location, returnFinalTarget);

return null;
}
catch (IOException ex) when (ex.HResult != -2147024773)
{
throw ExceptionFactory.FileNameCannotBeResolved(Location.FullPath,
_fileSystem.Execute.IsWindows ? -2147022975 : -2146232800);
}
return targetLocation != null ? New(targetLocation, _fileSystem) : null;
}
#endif

Expand Down
70 changes: 59 additions & 11 deletions Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -558,19 +558,28 @@ public IStorageContainer GetOrCreateContainer(

#if FEATURE_FILESYSTEM_LINK
/// <inheritdoc cref="IStorage.ResolveLinkTarget(IStorageLocation, bool)" />
public IStorageLocation? ResolveLinkTarget(IStorageLocation location,
bool returnFinalTarget = false)
public IStorageLocation? ResolveLinkTarget(
IStorageLocation location,
bool returnFinalTarget = false
)
{
if (!_containers.TryGetValue(location, out IStorageContainer? initialContainer)
|| initialContainer.LinkTarget == null)
{
return null;
}

IStorageLocation? nextLocation =
_fileSystem.Storage.GetLocation(initialContainer.LinkTarget);
IStorageLocation? nextLocation
= _fileSystem.Storage.GetLocation(initialContainer.LinkTarget);

if (!_containers.TryGetValue(nextLocation, out IStorageContainer? container))
{
return nextLocation;
}

ThrowOnLinkTypeChange(initialContainer, location, container);

if (returnFinalTarget && _containers.TryGetValue(nextLocation, out IStorageContainer? container) && container.LinkTarget != null)
if (returnFinalTarget && container.LinkTarget != null)
{
nextLocation = ResolveFinalLinkTarget(container, location);
}
Expand Down Expand Up @@ -1022,11 +1031,14 @@ private bool IncludeItemInEnumeration(
}

#if FEATURE_FILESYSTEM_LINK
private IStorageLocation? ResolveFinalLinkTarget(IStorageContainer container,
IStorageLocation originalLocation)
private IStorageLocation? ResolveFinalLinkTarget(
IStorageContainer container,
IStorageLocation originalLocation
)
{
int maxResolveLinks = _fileSystem.Execute.IsWindows ? 63 : 40;
IStorageLocation? nextLocation = null;

for (int i = 1; i < maxResolveLinks; i++)
{
if (container.LinkTarget == null)
Expand All @@ -1035,23 +1047,59 @@ private bool IncludeItemInEnumeration(
}

nextLocation = _fileSystem.Storage.GetLocation(container.LinkTarget);
if (!_containers.TryGetValue(nextLocation,
out IStorageContainer? nextContainer))

if (!_containers.TryGetValue(nextLocation, out IStorageContainer? nextContainer))
{
return nextLocation;
}

ThrowOnLinkTypeChange(container, originalLocation, nextContainer);

container = nextContainer;
}

if (container.LinkTarget != null)
{
throw ExceptionFactory.FileNameCannotBeResolved(
originalLocation.FullPath);
throw ExceptionFactory.FileNameCannotBeResolved(originalLocation.FullPath);
}

return nextLocation;
}

private void ThrowOnLinkTypeChange(
IStorageContainer previous,
IStorageLocation previousLocation,
IStorageContainer next
)
{
if (!_fileSystem.Execute.IsWindows)
{
return;
}

if (previous.Type == next.Type)
{
return;
}

switch (previous.Type)
{
case FileSystemTypes.File:
throw new UnauthorizedAccessException(
$"Access to the path '{previousLocation.FullPath}' is denied."
);
case FileSystemTypes.Directory:
// This message looks wonky but is correct
// See System.IO.Win32Marshal.GetExceptionForWin32Error in the default section
throw new IOException(
$"The directory name is invalid. : '{previousLocation.FullPath}'"
#if NET9_0_OR_GREATER
+ '.'
#endif
, -2147024629
);
}
}
#endif

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#if FEATURE_FILESYSTEM_LINK
using System.IO;
using System.Text.RegularExpressions;

namespace Testably.Abstractions.Tests.FileSystem.DirectoryInfo;

Expand Down Expand Up @@ -53,10 +54,7 @@ IFileSystemInfo innerLink

[Theory]
[AutoData]
public async Task ResolveLinkTarget_ShouldReturnImmediateFile(
string path,
string pathToTarget
)
public async Task ResolveLinkTarget_ShouldReturnImmediateFile(string path, string pathToTarget)
{
IDirectoryInfo targetDir = FileSystem.DirectoryInfo.New(pathToTarget);
targetDir.Create();
Expand Down Expand Up @@ -125,5 +123,37 @@ IFileSystemInfo outerLink

await That(resolvedTarget?.FullName).IsEqualTo(targetDir.FullName);
}

[Theory]
[AutoData]
public async Task ResolveLinkTarget_OfDifferentTypes_ShouldThrow(
string directoryName,
string fileLinkName,
string directoryLinkName
)
{
IDirectoryInfo targetDirectory = FileSystem.Directory.CreateDirectory(directoryName);

IFileSystemInfo fileSymLink = FileSystem.File.CreateSymbolicLink(
fileLinkName, targetDirectory.FullName
);

IFileSystemInfo dirSymLink = FileSystem.Directory.CreateSymbolicLink(
directoryLinkName, fileSymLink.FullName
);

if (Test.RunsOnWindows)
{
await That(() => dirSymLink.ResolveLinkTarget(true)).Throws<IOException>()
.WithMessage(
$@"^The directory name is invalid\. \: \'{Regex.Escape(dirSymLink.FullName)}\'\.?$"
).AsRegex().And.WithHResult(-2147024629);
}
else
{
await That(dirSymLink.ResolveLinkTarget(true)?.FullName)
.IsEqualTo(targetDirectory.FullName);
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@ string pathToTarget

[Theory]
[AutoData]
public async Task ResolveLinkTarget_ShouldReturnImmediateFile(
string path,
string pathToTarget
)
public async Task ResolveLinkTarget_ShouldReturnImmediateFile(string path, string pathToTarget)
{
IFileInfo targetFile = FileSystem.FileInfo.New(pathToTarget);
await targetFile.Create().DisposeAsync();
Expand Down Expand Up @@ -118,5 +115,34 @@ string pathToTarget

await That(resolvedTarget?.FullName).IsEqualTo(targetFile.FullName);
}

[Theory]
[AutoData]
public async Task ResolveLinkTarget_OfDifferentTypes_ShouldThrow(
string fileName,
string directoryName,
string fileLinkName
)
{
IFileInfo targetFile = FileSystem.FileInfo.New(fileName);
await targetFile.Create().DisposeAsync();

IFileSystemInfo dirSymLink
= FileSystem.Directory.CreateSymbolicLink(directoryName, targetFile.FullName);

IFileSystemInfo fileSymLink
= FileSystem.File.CreateSymbolicLink(fileLinkName, dirSymLink.FullName);

if (Test.RunsOnWindows)
{
await That(() => fileSymLink.ResolveLinkTarget(true))
.Throws<UnauthorizedAccessException>().WithMessage($"Access to the path '{fileSymLink.FullName}' is denied.");
}
else
{
await That(() => fileSymLink.ResolveLinkTarget(true))
.DoesNotThrow<UnauthorizedAccessException>();
}
}
}
#endif
Loading