From 649b77f295526a3532537e898ab67ea3af5927c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Mon, 15 Apr 2024 20:12:37 +0200 Subject: [PATCH 1/4] Implement - AltDirectorySeparatorChar - DirectorySeparatorChar - PathSeparator - VolumeSeparatorChar - GetInvalidFileNameChars() - GetInvalidPathChars() --- .../Helpers/Execute.LinuxPath.cs | 33 +- .../Helpers/Execute.MacPath.cs | 8 +- .../Helpers/Execute.NativePath.cs | 12 +- .../Helpers/Execute.SimulatedPath.cs | 411 ++++++++++++++++++ .../Helpers/Execute.WindowsPath.cs | 47 +- .../Helpers/PathHelperTests.cs | 253 +---------- 6 files changed, 481 insertions(+), 283 deletions(-) create mode 100644 Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs index a4e493f93..ca9b57dcd 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs @@ -1,21 +1,28 @@ -using System.IO; -#if FEATURE_SPAN -using System; -#endif - -namespace Testably.Abstractions.Testing.Helpers; +namespace Testably.Abstractions.Testing.Helpers; internal partial class Execute { - private class LinuxPath(MockFileSystem fileSystem) : NativePath(fileSystem) + private class LinuxPath(MockFileSystem fileSystem) : SimulatedPath(fileSystem) { -#if FEATURE_SPAN - /// - public override bool IsPathRooted(ReadOnlySpan path) - => IsPathRooted(path.ToString()); -#endif + /// + public override char AltDirectorySeparatorChar => '/'; + + /// + public override char DirectorySeparatorChar => '/'; + + /// + public override char PathSeparator => ':'; + + /// + public override char VolumeSeparatorChar => '/'; + + /// + public override char[] GetInvalidFileNameChars() => ['\0', '/']; + + /// + public override char[] GetInvalidPathChars() => ['\0']; - /// + /// public override bool IsPathRooted(string? path) => path?.Length > 0 && path[0] == '/'; } diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.MacPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.MacPath.cs index 1bca999cb..f5505ee54 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.MacPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.MacPath.cs @@ -1,10 +1,4 @@ -#if FEATURE_SPAN -#endif -#if FEATURE_FILESYSTEM_NET7 -using Testably.Abstractions.Testing.Storage; -#endif - -namespace Testably.Abstractions.Testing.Helpers; +namespace Testably.Abstractions.Testing.Helpers; internal partial class Execute { diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.NativePath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.NativePath.cs index 3ff65d12b..8f49b0519 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.NativePath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.NativePath.cs @@ -1,8 +1,8 @@ -#if FEATURE_SPAN +using System.Diagnostics.CodeAnalysis; +using System.IO; +#if FEATURE_SPAN using System; #endif -using System.Diagnostics.CodeAnalysis; -using System.IO; #if FEATURE_FILESYSTEM_NET7 using Testably.Abstractions.Testing.Storage; #endif @@ -11,7 +11,7 @@ namespace Testably.Abstractions.Testing.Helpers; internal partial class Execute { - private class NativePath(MockFileSystem fileSystem) : IPath + private sealed class NativePath(MockFileSystem fileSystem) : IPath { #region IPath Members @@ -228,12 +228,12 @@ public bool IsPathFullyQualified(string path) #if FEATURE_SPAN /// - public virtual bool IsPathRooted(ReadOnlySpan path) + public bool IsPathRooted(ReadOnlySpan path) => System.IO.Path.IsPathRooted(path); #endif /// - public virtual bool IsPathRooted(string? path) + public bool IsPathRooted(string? path) => System.IO.Path.IsPathRooted(path); #if FEATURE_PATH_JOIN diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs new file mode 100644 index 000000000..4d8d7aaa4 --- /dev/null +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs @@ -0,0 +1,411 @@ +using System; +using System.Diagnostics.CodeAnalysis; +#if FEATURE_FILESYSTEM_NET7 +using Testably.Abstractions.Testing.Storage; +#endif + +namespace Testably.Abstractions.Testing.Helpers; + +internal partial class Execute +{ + private abstract class SimulatedPath(MockFileSystem fileSystem) : IPath + { + #region IPath Members + + /// + public abstract char AltDirectorySeparatorChar { get; } + + /// + public abstract char DirectorySeparatorChar { get; } + + /// + public IFileSystem FileSystem => fileSystem; + + /// + public abstract char PathSeparator { get; } + + /// + public abstract char VolumeSeparatorChar { get; } + + /// + [return: NotNullIfNotNull("path")] + public string? ChangeExtension(string? path, string? extension) + => System.IO.Path.ChangeExtension(path, extension); + + /// + public string Combine(string path1, string path2) + { + if (path1 == null) + { + throw new ArgumentNullException(nameof(path1)); + } + + if (path2 == null) + { + throw new ArgumentNullException(nameof(path2)); + } + + return Combine([path1, path2]); + } + + /// + public string Combine(string path1, string path2, string path3) + { + if (path1 == null) + { + throw new ArgumentNullException(nameof(path1)); + } + + if (path2 == null) + { + throw new ArgumentNullException(nameof(path2)); + } + + if (path3 == null) + { + throw new ArgumentNullException(nameof(path3)); + } + + return Combine([path1, path2, path3]); + } + + /// + public string Combine(string path1, string path2, string path3, string path4) + { + if (path1 == null) + { + throw new ArgumentNullException(nameof(path1)); + } + + if (path2 == null) + { + throw new ArgumentNullException(nameof(path2)); + } + + if (path3 == null) + { + throw new ArgumentNullException(nameof(path3)); + } + + if (path4 == null) + { + throw new ArgumentNullException(nameof(path4)); + } + + return Combine([path1, path2, path3, path4]); + } + + /// + public string Combine(params string[] paths) + => System.IO.Path.Combine(paths); + +#if FEATURE_PATH_ADVANCED + /// + public bool EndsInDirectorySeparator(ReadOnlySpan path) + => EndsInDirectorySeparator(path.ToString()); +#endif + +#if FEATURE_PATH_ADVANCED + /// + public bool EndsInDirectorySeparator(string path) + => System.IO.Path.EndsInDirectorySeparator(path); +#endif + +#if FEATURE_FILESYSTEM_NET7 + /// + public bool Exists([NotNullWhen(true)] string? path) + { + if (string.IsNullOrEmpty(path)) + { + return false; + } + + return fileSystem.Storage.GetContainer(fileSystem.Storage.GetLocation(path)) + is not NullContainer; + } +#endif + +#if FEATURE_SPAN + /// + public ReadOnlySpan GetDirectoryName(ReadOnlySpan path) + => GetDirectoryName(path.ToString()); +#endif + + /// + public string? GetDirectoryName(string? path) + => System.IO.Path.GetDirectoryName(path); + +#if FEATURE_SPAN + /// + public ReadOnlySpan GetExtension(ReadOnlySpan path) + => GetExtension(path.ToString()); +#endif + + /// + [return: NotNullIfNotNull("path")] + public string? GetExtension(string? path) + => System.IO.Path.GetExtension(path); + +#if FEATURE_SPAN + /// + public ReadOnlySpan GetFileName(ReadOnlySpan path) + => GetFileName(path.ToString()); +#endif + + /// + [return: NotNullIfNotNull("path")] + public string? GetFileName(string? path) + => System.IO.Path.GetFileName(path); + +#if FEATURE_SPAN + /// + public ReadOnlySpan GetFileNameWithoutExtension(ReadOnlySpan path) + => GetFileNameWithoutExtension(path.ToString()); +#endif + + /// + [return: NotNullIfNotNull("path")] + public string? GetFileNameWithoutExtension(string? path) + => System.IO.Path.GetFileNameWithoutExtension(path); + + /// + public string GetFullPath(string path) + { + path.EnsureValidArgument(fileSystem, nameof(path)); + + string? pathRoot = System.IO.Path.GetPathRoot(path); + string? directoryRoot = + System.IO.Path.GetPathRoot(fileSystem.Storage.CurrentDirectory); + if (!string.IsNullOrEmpty(pathRoot) && !string.IsNullOrEmpty(directoryRoot)) + { + if (char.ToUpperInvariant(pathRoot[0]) != char.ToUpperInvariant(directoryRoot[0])) + { + return System.IO.Path.GetFullPath(path); + } + + if (pathRoot.Length < directoryRoot.Length) + { + path = path.Substring(pathRoot.Length); + } + } + + return System.IO.Path.GetFullPath(System.IO.Path.Combine( + fileSystem.Storage.CurrentDirectory, + path)); + } + +#if FEATURE_PATH_RELATIVE + /// + public string GetFullPath(string path, string basePath) + => System.IO.Path.GetFullPath(path, basePath); +#endif + + /// + public abstract char[] GetInvalidFileNameChars(); + + /// + public abstract char[] GetInvalidPathChars(); + +#if FEATURE_SPAN + /// + public ReadOnlySpan GetPathRoot(ReadOnlySpan path) + => GetPathRoot(path.ToString()); +#endif + + /// + public string? GetPathRoot(string? path) + => System.IO.Path.GetPathRoot(path); + + /// + public string GetRandomFileName() + => System.IO.Path.GetRandomFileName(); + +#if FEATURE_PATH_RELATIVE + /// + public string GetRelativePath(string relativeTo, string path) + { + relativeTo.EnsureValidArgument(fileSystem, nameof(relativeTo)); + path.EnsureValidArgument(fileSystem, nameof(path)); + + relativeTo = fileSystem.Execute.Path.GetFullPath(relativeTo); + path = fileSystem.Execute.Path.GetFullPath(path); + + return System.IO.Path.GetRelativePath(relativeTo, path); + } +#endif + + /// +#if !NETSTANDARD2_0 + [Obsolete( + "Insecure temporary file creation methods should not be used. Use `Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())` instead.")] +#endif + public string GetTempFileName() + => System.IO.Path.GetTempFileName(); + + /// + public string GetTempPath() + => System.IO.Path.GetTempPath(); + +#if FEATURE_SPAN + /// + public bool HasExtension(ReadOnlySpan path) + => HasExtension(path.ToString()); +#endif + + /// + public bool HasExtension([NotNullWhen(true)] string? path) + => System.IO.Path.HasExtension(path); + +#if FEATURE_SPAN + /// + public bool IsPathFullyQualified(ReadOnlySpan path) + => IsPathFullyQualified(path.ToString()); +#endif + +#if FEATURE_PATH_RELATIVE + /// + public bool IsPathFullyQualified(string path) + => System.IO.Path.IsPathFullyQualified(path); +#endif + +#if FEATURE_SPAN + /// + public bool IsPathRooted(ReadOnlySpan path) + => IsPathRooted(path.ToString()); +#endif + + /// + public abstract bool IsPathRooted(string? path); + +#if FEATURE_PATH_JOIN + /// + public string Join(ReadOnlySpan path1, ReadOnlySpan path2) + => Join(path1.ToString(), path2.ToString()); +#endif + +#if FEATURE_PATH_JOIN + /// + public string Join(ReadOnlySpan path1, + ReadOnlySpan path2, + ReadOnlySpan path3) + => Join(path1.ToString(), path2.ToString(), path3.ToString()); +#endif + +#if FEATURE_PATH_ADVANCED + /// + public string Join(ReadOnlySpan path1, + ReadOnlySpan path2, + ReadOnlySpan path3, + ReadOnlySpan path4) + => Join(path1.ToString(), path2.ToString(), path3.ToString(), path4.ToString()); +#endif + +#if FEATURE_PATH_ADVANCED + /// + public string Join(string? path1, string? path2) + { + if (string.IsNullOrEmpty(path1)) + { + return path2 ?? string.Empty; + } + + if (string.IsNullOrEmpty(path2)) + { + return path1; + } + + return Join([path1, path2]); + } +#endif + +#if FEATURE_PATH_ADVANCED + /// + public string Join(string? path1, string? path2, string? path3) + { + if (string.IsNullOrEmpty(path1)) + { + return Join(path2, path3); + } + + if (string.IsNullOrEmpty(path2)) + { + return Join(path1, path3); + } + + if (string.IsNullOrEmpty(path3)) + { + return Join(path1, path2); + } + + return Join([path1, path2, path3]); + } +#endif + +#if FEATURE_PATH_ADVANCED + /// + public string Join(string? path1, string? path2, string? path3, string? path4) + { + if (string.IsNullOrEmpty(path1)) + { + return Join(path2, path3, path4); + } + + if (string.IsNullOrEmpty(path2)) + { + return Join(path1, path3, path4); + } + + if (string.IsNullOrEmpty(path3)) + { + return Join(path1, path2, path4); + } + + if (string.IsNullOrEmpty(path4)) + { + return Join(path1, path2, path3); + } + + return Join([path1, path2, path3, path4]); + } +#endif + +#if FEATURE_PATH_ADVANCED + /// + public string Join(params string?[] paths) + => System.IO.Path.Join(paths); +#endif + +#if FEATURE_PATH_ADVANCED + /// + public ReadOnlySpan TrimEndingDirectorySeparator(ReadOnlySpan path) + => TrimEndingDirectorySeparator(path.ToString()); +#endif + +#if FEATURE_PATH_ADVANCED + /// + public string TrimEndingDirectorySeparator(string path) + => System.IO.Path.TrimEndingDirectorySeparator(path); +#endif + +#if FEATURE_PATH_JOIN + /// + public bool TryJoin(ReadOnlySpan path1, + ReadOnlySpan path2, + Span destination, + out int charsWritten) + => System.IO.Path.TryJoin(path1, path2, destination, out charsWritten); +#endif + +#if FEATURE_PATH_JOIN + /// + public bool TryJoin(ReadOnlySpan path1, + ReadOnlySpan path2, + ReadOnlySpan path3, + Span destination, + out int charsWritten) + => System.IO.Path.TryJoin(path1, path2, path3, destination, out charsWritten); +#endif + + #endregion + } +} diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.WindowsPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.WindowsPath.cs index b59170bfb..b6a84aaf8 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.WindowsPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.WindowsPath.cs @@ -1,21 +1,42 @@ -using System.IO; -#if FEATURE_SPAN -using System; -#endif - -namespace Testably.Abstractions.Testing.Helpers; +namespace Testably.Abstractions.Testing.Helpers; internal partial class Execute { - private sealed class WindowsPath(MockFileSystem fileSystem) : NativePath(fileSystem) + private sealed class WindowsPath(MockFileSystem fileSystem) : SimulatedPath(fileSystem) { -#if FEATURE_SPAN - /// - public override bool IsPathRooted(ReadOnlySpan path) - => IsPathRooted(path.ToString()); -#endif + /// + public override char AltDirectorySeparatorChar => '/'; + + /// + public override char DirectorySeparatorChar => '\\'; + + /// + public override char PathSeparator => ';'; + + /// + public override char VolumeSeparatorChar => ':'; + + /// + public override char[] GetInvalidFileNameChars() => + [ + '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, + (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, + (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, + (char)26, (char)27, (char)28, (char)29, (char)30, (char)31, ':', '*', '?', '\\', '/' + ]; + + /// + public override char[] GetInvalidPathChars() => + [ + '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, + (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, + (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, + (char)26, (char)27, (char)28, (char)29, (char)30, (char)31 + ]; - /// + /// public override bool IsPathRooted(string? path) { int? length = path?.Length; diff --git a/Tests/Testably.Abstractions.Testing.Tests/Helpers/PathHelperTests.cs b/Tests/Testably.Abstractions.Testing.Tests/Helpers/PathHelperTests.cs index 1fc2bb0c1..5362725d9 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/Helpers/PathHelperTests.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/Helpers/PathHelperTests.cs @@ -121,18 +121,19 @@ public void } [SkippableTheory] - [AutoData] + [InlineData('|')] + [InlineData((char)1)] + [InlineData((char)31)] public void ThrowCommonExceptionsIfPathIsInvalid_WithInvalidCharacters( - char[] invalidChars) + char invalidChar) { - // TODO: Enable this test again when the Execute method in MockFileSystem is writable - Skip.If(true, "Check how to update this test"); - _ = new FileSystemMockForPath(invalidChars); - string path = invalidChars[0] + "foo"; + MockFileSystem fileSystem = new(i => i + .SimulatingOperatingSystem(SimulationMode.Windows)); + string path = fileSystem.Path.GetFullPath(invalidChar + "path"); - Exception exception = Record.Exception(() => + Exception? exception = Record.Exception(() => { - path.EnsureValidFormat(null!); + path.EnsureValidFormat(fileSystem); }); #if NETFRAMEWORK @@ -159,240 +160,4 @@ public void exception.Should().BeOfType() .Which.Message.Should().Contain($"'{path}'"); } - - private sealed class FileSystemMockForPath(char[] invalidChars) : IFileSystem - { - #region IFileSystem Members - - public IDirectory Directory - => throw new NotSupportedException(); - - public IDirectoryInfoFactory DirectoryInfo - => throw new NotSupportedException(); - - public IDriveInfoFactory DriveInfo - => throw new NotSupportedException(); - - public IFile File - => throw new NotSupportedException(); - - public IFileInfoFactory FileInfo - => throw new NotSupportedException(); - - public IFileStreamFactory FileStream - => throw new NotSupportedException(); - - public IFileSystemWatcherFactory FileSystemWatcher - => throw new NotSupportedException(); - - public IPath Path { get; } = new PathMockWithInvalidChars(invalidChars); - - #endregion - - private sealed class PathMockWithInvalidChars(char[] invalidChars) : IPath - { - #region IPath Members - - public char AltDirectorySeparatorChar - => throw new NotSupportedException(); - - public char DirectorySeparatorChar - => throw new NotSupportedException(); - - public IFileSystem FileSystem - => throw new NotSupportedException(); - - public char PathSeparator - => throw new NotSupportedException(); - - public char VolumeSeparatorChar - => throw new NotSupportedException(); - - public string ChangeExtension(string? path, string? extension) - => throw new NotSupportedException(); - - public string Combine(string path1, string path2) - => throw new NotSupportedException(); - - public string Combine(string path1, string path2, string path3) - => throw new NotSupportedException(); - - public string Combine(string path1, string path2, string path3, string path4) - => throw new NotSupportedException(); - - public string Combine(params string[] paths) - => throw new NotSupportedException(); - -#if FEATURE_PATH_ADVANCED - public bool EndsInDirectorySeparator(ReadOnlySpan path) - => throw new NotSupportedException(); -#endif - -#if FEATURE_PATH_ADVANCED - public bool EndsInDirectorySeparator(string path) - => throw new NotSupportedException(); -#endif - -#if FEATURE_FILESYSTEM_NET7 - public bool Exists(string? path) - => throw new NotSupportedException(); -#endif - -#if FEATURE_SPAN - public ReadOnlySpan GetDirectoryName(ReadOnlySpan path) - => throw new NotSupportedException(); -#endif - - public string GetDirectoryName(string? path) - => throw new NotSupportedException(); - -#if FEATURE_SPAN - public ReadOnlySpan GetExtension(ReadOnlySpan path) - => throw new NotSupportedException(); -#endif - - public string GetExtension(string? path) - => throw new NotSupportedException(); - -#if FEATURE_SPAN - public ReadOnlySpan GetFileName(ReadOnlySpan path) - => throw new NotSupportedException(); -#endif - - public string GetFileName(string? path) - => throw new NotSupportedException(); - -#if FEATURE_SPAN - public ReadOnlySpan GetFileNameWithoutExtension(ReadOnlySpan path) - => throw new NotSupportedException(); -#endif - - public string GetFileNameWithoutExtension(string? path) - => throw new NotSupportedException(); - - public string GetFullPath(string path) - => path; - -#if FEATURE_PATH_RELATIVE - public string GetFullPath(string path, string basePath) - => throw new NotSupportedException(); -#endif - - public char[] GetInvalidFileNameChars() - => throw new NotSupportedException(); - - public char[] GetInvalidPathChars() - => invalidChars; - -#if FEATURE_SPAN - public ReadOnlySpan GetPathRoot(ReadOnlySpan path) - => throw new NotSupportedException(); -#endif - - public string GetPathRoot(string? path) - => throw new NotSupportedException(); - - public string GetRandomFileName() - => throw new NotSupportedException(); - -#if FEATURE_PATH_RELATIVE - public string GetRelativePath(string relativeTo, string path) - => throw new NotSupportedException(); -#endif - - public string GetTempFileName() - => throw new NotSupportedException(); - - public string GetTempPath() - => throw new NotSupportedException(); - -#if FEATURE_SPAN - public bool HasExtension(ReadOnlySpan path) - => throw new NotSupportedException(); -#endif - - public bool HasExtension(string? path) - => throw new NotSupportedException(); - -#if FEATURE_SPAN - public bool IsPathFullyQualified(ReadOnlySpan path) - => throw new NotSupportedException(); -#endif - -#if FEATURE_PATH_RELATIVE - public bool IsPathFullyQualified(string path) - => throw new NotSupportedException(); -#endif - -#if FEATURE_SPAN - public bool IsPathRooted(ReadOnlySpan path) - => throw new NotSupportedException(); -#endif - - public bool IsPathRooted(string? path) - => throw new NotSupportedException(); - -#if FEATURE_PATH_JOIN - public string Join(ReadOnlySpan path1, ReadOnlySpan path2) - => throw new NotSupportedException(); -#endif - -#if FEATURE_PATH_JOIN - public string Join(ReadOnlySpan path1, ReadOnlySpan path2, - ReadOnlySpan path3) - => throw new NotSupportedException(); -#endif - -#if FEATURE_PATH_ADVANCED - public string Join(string? path1, string? path2) - => throw new NotSupportedException(); -#endif - -#if FEATURE_PATH_ADVANCED - public string Join(string? path1, string? path2, string? path3) - => throw new NotSupportedException(); -#endif - -#if FEATURE_PATH_ADVANCED - public string Join(params string?[] paths) - => throw new NotSupportedException(); -#endif - -#if FEATURE_PATH_ADVANCED - public string Join(ReadOnlySpan path1, ReadOnlySpan path2, - ReadOnlySpan path3, ReadOnlySpan path4) - => throw new NotSupportedException(); -#endif - -#if FEATURE_PATH_ADVANCED - public string Join(string? path1, string? path2, string? path3, string? path4) - => throw new NotSupportedException(); -#endif - -#if FEATURE_PATH_ADVANCED - public ReadOnlySpan TrimEndingDirectorySeparator(ReadOnlySpan path) - => throw new NotSupportedException(); -#endif - -#if FEATURE_PATH_ADVANCED - public string TrimEndingDirectorySeparator(string path) - => throw new NotSupportedException(); -#endif - -#if FEATURE_PATH_JOIN - public bool TryJoin(ReadOnlySpan path1, ReadOnlySpan path2, - Span destination, out int charsWritten) - => throw new NotSupportedException(); -#endif - -#if FEATURE_PATH_JOIN - public bool TryJoin(ReadOnlySpan path1, ReadOnlySpan path2, - ReadOnlySpan path3, Span destination, - out int charsWritten) - => throw new NotSupportedException(); -#endif - - #endregion - } - } } From 2625b023eacbc5dcff2fd77a25c59e2d8c64a3a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Tue, 16 Apr 2024 07:40:20 +0200 Subject: [PATCH 2/4] Fix failing test --- .../Helpers/Execute.SimulatedPath.cs | 30 ++++++++++++------- .../Helpers/PathHelperTests.cs | 6 ++-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs index 4d8d7aaa4..1eec0b017 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs @@ -45,7 +45,7 @@ public string Combine(string path1, string path2) throw new ArgumentNullException(nameof(path2)); } - return Combine([path1, path2]); + return CombineInternal([path1, path2]); } /// @@ -66,7 +66,7 @@ public string Combine(string path1, string path2, string path3) throw new ArgumentNullException(nameof(path3)); } - return Combine([path1, path2, path3]); + return CombineInternal([path1, path2, path3]); } /// @@ -92,12 +92,12 @@ public string Combine(string path1, string path2, string path3, string path4) throw new ArgumentNullException(nameof(path4)); } - return Combine([path1, path2, path3, path4]); + return CombineInternal([path1, path2, path3, path4]); } /// public string Combine(params string[] paths) - => System.IO.Path.Combine(paths); + => CombineInternal(paths); #if FEATURE_PATH_ADVANCED /// @@ -280,7 +280,7 @@ public bool IsPathRooted(ReadOnlySpan path) #if FEATURE_PATH_JOIN /// public string Join(ReadOnlySpan path1, ReadOnlySpan path2) - => Join(path1.ToString(), path2.ToString()); + => System.IO.Path.Join(path1, path2); #endif #if FEATURE_PATH_JOIN @@ -288,7 +288,7 @@ public string Join(ReadOnlySpan path1, ReadOnlySpan path2) public string Join(ReadOnlySpan path1, ReadOnlySpan path2, ReadOnlySpan path3) - => Join(path1.ToString(), path2.ToString(), path3.ToString()); + => System.IO.Path.Join(path1, path2, path3); #endif #if FEATURE_PATH_ADVANCED @@ -297,7 +297,7 @@ public string Join(ReadOnlySpan path1, ReadOnlySpan path2, ReadOnlySpan path3, ReadOnlySpan path4) - => Join(path1.ToString(), path2.ToString(), path3.ToString(), path4.ToString()); + => JoinInternal([path1.ToString(), path2.ToString(), path3.ToString(), path4.ToString()]); #endif #if FEATURE_PATH_ADVANCED @@ -314,7 +314,7 @@ public string Join(string? path1, string? path2) return path1; } - return Join([path1, path2]); + return JoinInternal([path1, path2]); } #endif @@ -337,7 +337,7 @@ public string Join(string? path1, string? path2, string? path3) return Join(path1, path2); } - return Join([path1, path2, path3]); + return JoinInternal([path1, path2, path3]); } #endif @@ -365,14 +365,14 @@ public string Join(string? path1, string? path2, string? path3, string? path4) return Join(path1, path2, path3); } - return Join([path1, path2, path3, path4]); + return JoinInternal([path1, path2, path3, path4]); } #endif #if FEATURE_PATH_ADVANCED /// public string Join(params string?[] paths) - => System.IO.Path.Join(paths); + => JoinInternal(paths); #endif #if FEATURE_PATH_ADVANCED @@ -407,5 +407,13 @@ public bool TryJoin(ReadOnlySpan path1, #endif #endregion + + private static string CombineInternal(string[] paths) + => System.IO.Path.Combine(paths); + +#if FEATURE_PATH_ADVANCED + private static string JoinInternal(string?[] paths) + => System.IO.Path.Join(paths); +#endif } } diff --git a/Tests/Testably.Abstractions.Testing.Tests/Helpers/PathHelperTests.cs b/Tests/Testably.Abstractions.Testing.Tests/Helpers/PathHelperTests.cs index 5362725d9..8d40817d2 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/Helpers/PathHelperTests.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/Helpers/PathHelperTests.cs @@ -129,7 +129,7 @@ public void ThrowCommonExceptionsIfPathIsInvalid_WithInvalidCharacters( { MockFileSystem fileSystem = new(i => i .SimulatingOperatingSystem(SimulationMode.Windows)); - string path = fileSystem.Path.GetFullPath(invalidChar + "path"); + string path = invalidChar + "path"; Exception? exception = Record.Exception(() => { @@ -138,10 +138,10 @@ public void ThrowCommonExceptionsIfPathIsInvalid_WithInvalidCharacters( #if NETFRAMEWORK exception.Should().BeOfType() - .Which.Message.Should().Contain($"'{path}'"); + .Which.Message.Should().Contain(path); #else exception.Should().BeOfType() - .Which.Message.Should().Contain($"'{path}'"); + .Which.Message.Should().Contain(path); #endif } From ba4546143b78a8227dd8a89d983222f397c916b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Tue, 16 Apr 2024 08:14:03 +0200 Subject: [PATCH 3/4] Add GetTempPath() --- .../Helpers/Execute.LinuxPath.cs | 4 ++++ .../Helpers/Execute.SimulatedPath.cs | 3 +-- .../Helpers/Execute.WindowsPath.cs | 4 ++++ .../Helpers/PathHelperTests.cs | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs index ca9b57dcd..9a6dff140 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs @@ -22,6 +22,10 @@ private class LinuxPath(MockFileSystem fileSystem) : SimulatedPath(fileSystem) /// public override char[] GetInvalidPathChars() => ['\0']; + /// + public override string GetTempPath() + => "/tmp/"; + /// public override bool IsPathRooted(string? path) => path?.Length > 0 && path[0] == '/'; diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs index 1eec0b017..f5b8e3ac1 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs @@ -243,8 +243,7 @@ public string GetTempFileName() => System.IO.Path.GetTempFileName(); /// - public string GetTempPath() - => System.IO.Path.GetTempPath(); + public abstract string GetTempPath(); #if FEATURE_SPAN /// diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.WindowsPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.WindowsPath.cs index b6a84aaf8..d3685d534 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.WindowsPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.WindowsPath.cs @@ -36,6 +36,10 @@ public override char[] GetInvalidPathChars() => (char)26, (char)27, (char)28, (char)29, (char)30, (char)31 ]; + /// + public override string GetTempPath() + => @"C:\Windows\Temp"; + /// public override bool IsPathRooted(string? path) { diff --git a/Tests/Testably.Abstractions.Testing.Tests/Helpers/PathHelperTests.cs b/Tests/Testably.Abstractions.Testing.Tests/Helpers/PathHelperTests.cs index 8d40817d2..95727aca4 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/Helpers/PathHelperTests.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/Helpers/PathHelperTests.cs @@ -138,7 +138,7 @@ public void ThrowCommonExceptionsIfPathIsInvalid_WithInvalidCharacters( #if NETFRAMEWORK exception.Should().BeOfType() - .Which.Message.Should().Contain(path); + .Which.Message.Should().Contain("path"); #else exception.Should().BeOfType() .Which.Message.Should().Contain(path); From 46b0458e8123fcbc97b13881d1746f5247569b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Tue, 16 Apr 2024 16:10:01 +0200 Subject: [PATCH 4/4] Add GetPathRoot() --- .../Helpers/Execute.LinuxPath.cs | 13 +++++++++++++ .../Helpers/Execute.SimulatedPath.cs | 3 +-- .../Helpers/Execute.WindowsPath.cs | 13 +++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs index 9a6dff140..d5d230e9d 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.LinuxPath.cs @@ -22,6 +22,19 @@ private class LinuxPath(MockFileSystem fileSystem) : SimulatedPath(fileSystem) /// public override char[] GetInvalidPathChars() => ['\0']; + /// + public override string? GetPathRoot(string? path) + { + if (string.IsNullOrEmpty(path)) + { + return null; + } + + return IsPathRooted(path) + ? $"{DirectorySeparatorChar}" + : string.Empty; + } + /// public override string GetTempPath() => "/tmp/"; diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs index f5b8e3ac1..30ff88421 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs @@ -213,8 +213,7 @@ public ReadOnlySpan GetPathRoot(ReadOnlySpan path) #endif /// - public string? GetPathRoot(string? path) - => System.IO.Path.GetPathRoot(path); + public abstract string? GetPathRoot(string? path); /// public string GetRandomFileName() diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.WindowsPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.WindowsPath.cs index d3685d534..e18a91556 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.WindowsPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.WindowsPath.cs @@ -36,6 +36,19 @@ public override char[] GetInvalidPathChars() => (char)26, (char)27, (char)28, (char)29, (char)30, (char)31 ]; + /// + public override string? GetPathRoot(string? path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return null; + } + + return IsPathRooted(path) + ? path.Substring(0,3) + : string.Empty; + } + /// public override string GetTempPath() => @"C:\Windows\Temp";