Skip to content

Commit

Permalink
Merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
vbreuss committed Apr 20, 2024
2 parents 7075020 + c9d4256 commit 4aecdaf
Show file tree
Hide file tree
Showing 11 changed files with 860 additions and 45 deletions.
15 changes: 15 additions & 0 deletions Source/Testably.Abstractions.Testing/Helpers/ExceptionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ internal static ArgumentException AppendAccessOnlyInWriteOnlyMode(
#endif
};

internal static ArgumentException BasePathNotFullyQualified(string paramName)
=> new("Basepath argument is not fully qualified.", paramName)
{
#if FEATURE_EXCEPTION_HRESULT
HResult = -2147024809
#endif
};

internal static IOException CannotCreateFileAsAlreadyExists(Execute execute, string path)
=> new(
$"Cannot create '{path}' because a file or directory with the same name already exists.",
Expand Down Expand Up @@ -127,6 +135,13 @@ internal static NotSupportedException NotSupportedSafeFileHandle()
internal static NotSupportedException NotSupportedTimerWrapping()
=> new("You cannot wrap an existing Timer in the MockTimeSystem instance!");

internal static ArgumentException NullCharacterInPath(string paramName)
#if NET8_0_OR_GREATER
=> new("Null character in path.", paramName);
#else
=> new("Illegal characters in path.", paramName);
#endif

internal static PlatformNotSupportedException OperationNotSupportedOnThisPlatform()
=> new("Operation is not supported on this platform.")
{
Expand Down
116 changes: 115 additions & 1 deletion Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace Testably.Abstractions.Testing.Helpers;
using System;
using System.Text;

namespace Testably.Abstractions.Testing.Helpers;

internal partial class Execute
{
Expand All @@ -16,6 +19,54 @@ private class LinuxPath(MockFileSystem fileSystem) : SimulatedPath(fileSystem)
/// <inheritdoc cref="IPath.VolumeSeparatorChar" />
public override char VolumeSeparatorChar => '/';

private readonly MockFileSystem _fileSystem = fileSystem;

/// <inheritdoc cref="IPath.GetFullPath(string)" />
public override string GetFullPath(string path)
{
path.EnsureValidArgument(_fileSystem, nameof(path));

if (!IsPathRooted(path))
{
path = Combine(_fileSystem.Storage.CurrentDirectory, path);
}

// We would ideally use realpath to do this, but it resolves symlinks and requires that the file actually exist.
string collapsedString = RemoveRelativeSegments(path, GetRootLength(path));

string result = collapsedString.Length == 0
? $"{DirectorySeparatorChar}"
: collapsedString;

if (result.Contains('\0', StringComparison.Ordinal))
{
throw ExceptionFactory.NullCharacterInPath(nameof(path));
}

return result;
}

#if FEATURE_PATH_RELATIVE
/// <inheritdoc cref="IPath.GetFullPath(string, string)" />
public override string GetFullPath(string path, string basePath)
{
path.EnsureValidArgument(_fileSystem, nameof(path));
basePath.EnsureValidArgument(_fileSystem, nameof(basePath));

if (!IsPathFullyQualified(basePath))
{
throw ExceptionFactory.BasePathNotFullyQualified(nameof(basePath));
}

if (IsPathFullyQualified(path))
{
return GetFullPath(path);
}

return GetFullPath(Combine(basePath, path));
}
#endif

/// <inheritdoc cref="IPath.GetInvalidFileNameChars()" />
public override char[] GetInvalidFileNameChars() => ['\0', '/'];

Expand Down Expand Up @@ -43,10 +94,73 @@ public override string GetTempPath()
public override bool IsPathRooted(string? path)
=> path?.Length > 0 && path[0] == '/';

/// <summary>
/// https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/Common/src/System/IO/PathInternal.Unix.cs#L22
/// </summary>
protected override int GetRootLength(string path)
{
return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0;
}

/// <summary>
/// https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/Common/src/System/IO/PathInternal.Unix.cs#L27
/// </summary>
protected override bool IsDirectorySeparator(char c)
=> c == DirectorySeparatorChar;

/// <summary>
/// https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/Common/src/System/IO/PathInternal.Unix.cs#L89
/// </summary>
protected override bool IsEffectivelyEmpty(string path)
=> string.IsNullOrEmpty(path);

/// <summary>
/// https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/Common/src/System/IO/PathInternal.Unix.cs#L77
/// </summary>
protected override bool IsPartiallyQualified(string path)
=> !IsPathRooted(path);

/// <summary>
/// https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/Common/src/System/IO/PathInternal.Unix.cs#L39
/// </summary>
protected override string NormalizeDirectorySeparators(string path)
{
bool IsAlreadyNormalized()
{
for (int i = 0; i < path.Length - 1; i++)
{
if (IsDirectorySeparator(path[i]) &&
IsDirectorySeparator(path[i + 1]))
{
return false;
}
}

return true;
}

if (IsAlreadyNormalized())
{
return path;
}

StringBuilder builder = new(path.Length);

for (int j = 0; j < path.Length - 1; j++)
{
char current = path[j];

if (IsDirectorySeparator(current)
&& IsDirectorySeparator(path[j + 1]))
{
continue;
}

builder.Append(current);
}

builder.Append(path[path.Length - 1]);
return builder.ToString();
}
}
}
Loading

0 comments on commit 4aecdaf

Please sign in to comment.