From 60e4ad2e9955281bf0cccc6282e9d744dea1a7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Fri, 19 Apr 2024 16:06:56 +0200 Subject: [PATCH 1/3] Add `TryJoin` --- .../Helpers/Execute.SimulatedPath.cs | 91 ++++++++++++++++++- .../FileSystemClassGenerator.cs | 3 +- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs index af3088a04..828238292 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text; @@ -407,7 +408,45 @@ public bool TryJoin(ReadOnlySpan path1, ReadOnlySpan path2, Span destination, out int charsWritten) - => System.IO.Path.TryJoin(path1, path2, destination, out charsWritten); + { + charsWritten = 0; + if (path1.Length == 0 && path2.Length == 0) + { + return true; + } + + if (path1.Length == 0 || path2.Length == 0) + { + ref ReadOnlySpan pathToUse = ref path1.Length == 0 ? ref path2 : ref path1; + if (destination.Length < pathToUse.Length) + { + return false; + } + + pathToUse.CopyTo(destination); + charsWritten = pathToUse.Length; + return true; + } + + bool needsSeparator = + !(path1.Length > 0 && IsDirectorySeparator(path1[path1.Length - 1]) || path2.Length > 0 && IsDirectorySeparator(path2[0])); + int charsNeeded = path1.Length + path2.Length + (needsSeparator ? 1 : 0); + if (destination.Length < charsNeeded) + { + return false; + } + + path1.CopyTo(destination); + if (needsSeparator) + { + destination[path1.Length] = DirectorySeparatorChar; + } + + path2.CopyTo(destination.Slice(path1.Length + (needsSeparator ? 1 : 0))); + + charsWritten = charsNeeded; + return true; + } #endif #if FEATURE_PATH_JOIN @@ -417,7 +456,55 @@ public bool TryJoin(ReadOnlySpan path1, ReadOnlySpan path3, Span destination, out int charsWritten) - => System.IO.Path.TryJoin(path1, path2, path3, destination, out charsWritten); + { + charsWritten = 0; + if (path1.Length == 0 && path2.Length == 0 && path3.Length == 0) + { + return true; + } + + if (path1.Length == 0) + { + return TryJoin(path2, path3, destination, out charsWritten); + } + + if (path2.Length == 0) + { + return TryJoin(path1, path3, destination, out charsWritten); + } + + if (path3.Length == 0) + { + return TryJoin(path1, path2, destination, out charsWritten); + } + int neededSeparators = + path1.Length > 0 && IsDirectorySeparator(path1[path1.Length - 1]) || path2.Length > 0 && IsDirectorySeparator(path2[0]) ? 0 : 1; + bool needsSecondSeparator = + !(path2.Length > 0 && IsDirectorySeparator(path2[path2.Length - 1]) || path3.Length > 0 && IsDirectorySeparator(path3[0])); + if (needsSecondSeparator) + { + neededSeparators++; + } + + int charsNeeded = path1.Length + path2.Length + path3.Length + neededSeparators; + if (destination.Length < charsNeeded) + { + return false; + } + + bool result = TryJoin(path1, path2, destination, out charsWritten); + Debug.Assert(result, "should never fail joining first two paths"); + + if (needsSecondSeparator) + { + destination[charsWritten++] = DirectorySeparatorChar; + } + + path3.CopyTo(destination.Slice(charsWritten)); + charsWritten += path3.Length; + + return true; + } #endif #endregion diff --git a/Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs b/Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs index 8757eb731..adfe34e6c 100644 --- a/Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs +++ b/Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs @@ -241,7 +241,8 @@ private bool IncludeSimulatedTests(ClassModel @class) "GetTempPathTests", "IsPathRootedTests", "JoinTests", - "Tests" + "Tests", + "TryJoinTests" ]; return @class.Namespace .StartsWith("Testably.Abstractions.Tests.FileSystem.Path", StringComparison.Ordinal) From 8b72b3f75d75a98ddad1462256aeadd56bbfff50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Fri, 19 Apr 2024 16:22:30 +0200 Subject: [PATCH 2/3] Improve tests --- .../Helpers/Execute.SimulatedPath.cs | 87 +++---------------- .../FileSystem/Path/TryJoinTests.cs | 49 +++++++++++ 2 files changed, 60 insertions(+), 76 deletions(-) diff --git a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs index 828238292..8efbaaf1d 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs @@ -2,7 +2,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Text; - #if FEATURE_FILESYSTEM_NET7 using Testably.Abstractions.Testing.Storage; #endif @@ -409,42 +408,15 @@ public bool TryJoin(ReadOnlySpan path1, Span destination, out int charsWritten) { - charsWritten = 0; - if (path1.Length == 0 && path2.Length == 0) - { - return true; - } - - if (path1.Length == 0 || path2.Length == 0) - { - ref ReadOnlySpan pathToUse = ref path1.Length == 0 ? ref path2 : ref path1; - if (destination.Length < pathToUse.Length) - { - return false; - } - - pathToUse.CopyTo(destination); - charsWritten = pathToUse.Length; - return true; - } - - bool needsSeparator = - !(path1.Length > 0 && IsDirectorySeparator(path1[path1.Length - 1]) || path2.Length > 0 && IsDirectorySeparator(path2[0])); - int charsNeeded = path1.Length + path2.Length + (needsSeparator ? 1 : 0); - if (destination.Length < charsNeeded) + string result = Join(path1, path2); + if (destination.Length < result.Length) { + charsWritten = 0; return false; } - path1.CopyTo(destination); - if (needsSeparator) - { - destination[path1.Length] = DirectorySeparatorChar; - } - - path2.CopyTo(destination.Slice(path1.Length + (needsSeparator ? 1 : 0))); - - charsWritten = charsNeeded; + result.AsSpan().CopyTo(destination); + charsWritten = result.Length; return true; } #endif @@ -457,52 +429,15 @@ public bool TryJoin(ReadOnlySpan path1, Span destination, out int charsWritten) { - charsWritten = 0; - if (path1.Length == 0 && path2.Length == 0 && path3.Length == 0) - { - return true; - } - - if (path1.Length == 0) - { - return TryJoin(path2, path3, destination, out charsWritten); - } - - if (path2.Length == 0) - { - return TryJoin(path1, path3, destination, out charsWritten); - } - - if (path3.Length == 0) - { - return TryJoin(path1, path2, destination, out charsWritten); - } - int neededSeparators = - path1.Length > 0 && IsDirectorySeparator(path1[path1.Length - 1]) || path2.Length > 0 && IsDirectorySeparator(path2[0]) ? 0 : 1; - bool needsSecondSeparator = - !(path2.Length > 0 && IsDirectorySeparator(path2[path2.Length - 1]) || path3.Length > 0 && IsDirectorySeparator(path3[0])); - if (needsSecondSeparator) - { - neededSeparators++; - } - - int charsNeeded = path1.Length + path2.Length + path3.Length + neededSeparators; - if (destination.Length < charsNeeded) + string result = Join(path1, path2, path3); + if (destination.Length < result.Length) { + charsWritten = 0; return false; } - bool result = TryJoin(path1, path2, destination, out charsWritten); - Debug.Assert(result, "should never fail joining first two paths"); - - if (needsSecondSeparator) - { - destination[charsWritten++] = DirectorySeparatorChar; - } - - path3.CopyTo(destination.Slice(charsWritten)); - charsWritten += path3.Length; - + result.AsSpan().CopyTo(destination); + charsWritten = result.Length; return true; } #endif @@ -527,7 +462,7 @@ private string JoinInternal(string?[] paths) return string.Empty; } - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); foreach (string? path in paths) { if (string.IsNullOrEmpty(path)) diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Path/TryJoinTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Path/TryJoinTests.cs index c484b35cc..affcb99b9 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Path/TryJoinTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Path/TryJoinTests.cs @@ -27,6 +27,29 @@ public void TryJoin_2Paths_BufferTooLittle_ShouldReturnFalse( charsWritten.Should().Be(0); } + [SkippableTheory] + [InlineAutoData("/foo/", "/bar/", "/foo//bar/")] + [InlineAutoData("foo/", "/bar", "foo//bar")] + [InlineAutoData("foo/", "bar", "foo/bar")] + [InlineAutoData("foo", "/bar", "foo/bar")] + [InlineAutoData("foo", "bar", "foo/bar")] + [InlineAutoData("/foo", "bar/", "/foo/bar/")] + public void TryJoin_2Paths_ShouldReturnExpectedResult( + string path1, string path2, string expectedResult) + { + path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar); + path2 = path2.Replace('/', FileSystem.Path.DirectorySeparatorChar); + expectedResult = expectedResult.Replace('/', FileSystem.Path.DirectorySeparatorChar); + char[] buffer = new char[expectedResult.Length]; + Span destination = new(buffer); + + bool result = FileSystem.Path.TryJoin(path1, path2, destination, out int charsWritten); + + result.Should().BeTrue(); + charsWritten.Should().Be(expectedResult.Length); + destination.Slice(0, charsWritten).ToString().Should().Be(expectedResult); + } + [SkippableTheory] [AutoData] public void TryJoin_2Paths_ShouldReturnPathsCombinedByDirectorySeparatorChar( @@ -72,6 +95,32 @@ public void TryJoin_3Paths_BufferTooLittle_ShouldReturnFalse( charsWritten.Should().Be(0); } + [SkippableTheory] + [InlineAutoData("/foo/", "/bar/", "/baz/", "/foo//bar//baz/")] + [InlineAutoData("foo/", "/bar/", "/baz", "foo//bar//baz")] + [InlineAutoData("foo/", "bar", "/baz", "foo/bar/baz")] + [InlineAutoData("foo", "/bar", "/baz", "foo/bar/baz")] + [InlineAutoData("foo", "/bar/", "baz", "foo/bar/baz")] + [InlineAutoData("foo", "bar", "baz", "foo/bar/baz")] + [InlineAutoData("/foo", "bar", "baz/", "/foo/bar/baz/")] + public void TryJoin_3Paths_ShouldReturnExpectedResult( + string path1, string path2, string path3, string expectedResult) + { + path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar); + path2 = path2.Replace('/', FileSystem.Path.DirectorySeparatorChar); + path3 = path3.Replace('/', FileSystem.Path.DirectorySeparatorChar); + expectedResult = expectedResult.Replace('/', FileSystem.Path.DirectorySeparatorChar); + char[] buffer = new char[expectedResult.Length]; + Span destination = new(buffer); + + bool result = + FileSystem.Path.TryJoin(path1, path2, path3, destination, out int charsWritten); + + result.Should().BeTrue(); + charsWritten.Should().Be(expectedResult.Length); + destination.Slice(0, charsWritten).ToString().Should().Be(expectedResult); + } + [SkippableTheory] [AutoData] public void TryJoin_3Paths_ShouldReturnPathsCombinedByDirectorySeparatorChar( From f7b113eee81c669a1ff06137db836ebd74f7e335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Fri, 19 Apr 2024 16:34:44 +0200 Subject: [PATCH 3/3] Use `.AsSpan()` --- .../FileSystem/Path/TryJoinTests.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Path/TryJoinTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Path/TryJoinTests.cs index affcb99b9..8dd934b0b 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Path/TryJoinTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Path/TryJoinTests.cs @@ -43,7 +43,11 @@ public void TryJoin_2Paths_ShouldReturnExpectedResult( char[] buffer = new char[expectedResult.Length]; Span destination = new(buffer); - bool result = FileSystem.Path.TryJoin(path1, path2, destination, out int charsWritten); + bool result = FileSystem.Path.TryJoin( + path1.AsSpan(), + path2.AsSpan(), + destination, + out int charsWritten); result.Should().BeTrue(); charsWritten.Should().Be(expectedResult.Length); @@ -113,8 +117,12 @@ public void TryJoin_3Paths_ShouldReturnExpectedResult( char[] buffer = new char[expectedResult.Length]; Span destination = new(buffer); - bool result = - FileSystem.Path.TryJoin(path1, path2, path3, destination, out int charsWritten); + bool result = FileSystem.Path.TryJoin( + path1.AsSpan(), + path2.AsSpan(), + path3.AsSpan(), + destination, + out int charsWritten); result.Should().BeTrue(); charsWritten.Should().Be(expectedResult.Length);