From 0d345a2e7a52e48dd708eb5d05111903a6acc4fe Mon Sep 17 00:00:00 2001 From: "p.wintrich" Date: Tue, 17 Feb 2026 08:36:45 +0100 Subject: [PATCH 01/10] fix: fixed removing current directory not throwing ioexception --- .../Helpers/ExceptionFactory.cs | 16 +++++++++ .../Storage/InMemoryStorage.cs | 26 ++++++++++++++- .../Directory/RemoveCurrentDirectoryTests.cs | 33 +++++++++++++++++++ .../RemoveCurrentDirectoryTests.cs | 33 +++++++++++++++++++ 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 Tests/Testably.Abstractions.Tests/FileSystem/Directory/RemoveCurrentDirectoryTests.cs create mode 100644 Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/RemoveCurrentDirectoryTests.cs diff --git a/Source/Testably.Abstractions.Testing/Helpers/ExceptionFactory.cs b/Source/Testably.Abstractions.Testing/Helpers/ExceptionFactory.cs index 61b07112..364abeee 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/ExceptionFactory.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/ExceptionFactory.cs @@ -85,6 +85,22 @@ internal static FileNotFoundException FileNotFound(string path) #endif }; + internal static IOException FileSharingViolation() + => new("The process cannot access the file because it is being used by another process.") + { +#if FEATURE_EXCEPTION_HRESULT + HResult = -2147024864, +#endif + }; + + internal static IOException FileSharingViolation(string path) + => new($"The process cannot access the file '{path}' because it is being used by another process.") + { +#if FEATURE_EXCEPTION_HRESULT + HResult = -2147024864, +#endif + }; + internal static ArgumentException HandleIsInvalid(string? paramName = "handle") => new("Invalid handle.", paramName); diff --git a/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs b/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs index 0312254e..4a0aa710 100644 --- a/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs +++ b/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs @@ -144,7 +144,9 @@ public bool DeleteContainer( } ValidateContainerType(container.Type, expectedType, _fileSystem.Execute, location); - + + ValidateHandle(location.FullPath, location.FullPath); + if (container.Type == FileSystemTypes.Directory) { IEnumerable children = @@ -991,6 +993,8 @@ private bool IncludeItemInEnumeration( { return null; } + + ValidateHandle(source.FullPath); if (container.Type == FileSystemTypes.Directory && source.FullPath.Equals(destination.FullPath, _fileSystem.Execute.IsNetFramework @@ -1197,6 +1201,26 @@ private static void ValidateContainerType( } } + private void ValidateHandle(string fullPath, string? errorPath = null) + { + if (!_fileSystem.Execute.IsWindows) + { + return; + } + + string path = fullPath.TrimEnd(_fileSystem.Path.DirectorySeparatorChar) + .TrimEnd(_fileSystem.Path.AltDirectorySeparatorChar); + + if (_fileSystem.Directory.GetCurrentDirectory().StartsWith( + path, _fileSystem.Execute.StringComparisonMode + )) + { + throw string.IsNullOrEmpty(errorPath) + ? ExceptionFactory.FileSharingViolation() + : ExceptionFactory.FileSharingViolation(errorPath); + } + } + private static void ValidateExpression(string expression) { if (expression.Contains('\0', StringComparison.Ordinal)) diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/RemoveCurrentDirectoryTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/RemoveCurrentDirectoryTests.cs new file mode 100644 index 00000000..6a2374fd --- /dev/null +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/RemoveCurrentDirectoryTests.cs @@ -0,0 +1,33 @@ +using System.IO; + +namespace Testably.Abstractions.Tests.FileSystem.Directory; + +[FileSystemTests] +public partial class RemoveCurrentDirectoryTests +{ + [Fact] + public async Task MoveCurrentDirectory_ShouldThrowIOException_WithoutPath() + { + Skip.IfNot(Test.RunsOnWindows); + + string directory = FileSystem.Directory.GetCurrentDirectory(); + + await That(() => FileSystem.Directory.Move(directory, "new")).ThrowsExactly() + .Which.HasMessage( + "The process cannot access the file because it is being used by another process." + ); + } + + [Fact] + public async Task DeleteCurrentDirectory_ShouldThrowIOException_WithPath() + { + Skip.IfNot(Test.RunsOnWindows); + + string directory = FileSystem.Directory.GetCurrentDirectory(); + + await That(() => FileSystem.Directory.Delete(directory)).ThrowsExactly().Which + .HasMessage( + $"The process cannot access the file '{directory}' because it is being used by another process." + ); + } +} diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/RemoveCurrentDirectoryTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/RemoveCurrentDirectoryTests.cs new file mode 100644 index 00000000..d2a8c382 --- /dev/null +++ b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/RemoveCurrentDirectoryTests.cs @@ -0,0 +1,33 @@ +using System.IO; + +namespace Testably.Abstractions.Tests.FileSystem.DirectoryInfo; + +[FileSystemTests] +public partial class RemoveCurrentDirectoryTests +{ + [Fact] + public async Task MoveCurrentDirectory_ShouldThrowIOException_WithoutPath() + { + Skip.IfNot(Test.RunsOnWindows); + + IDirectoryInfo directoryInfo + = FileSystem.DirectoryInfo.New(FileSystem.Directory.GetCurrentDirectory()); + + await That(() => directoryInfo.MoveTo("new")).ThrowsExactly().Which.HasMessage( + "The process cannot access the file because it is being used by another process." + ); + } + + [Fact] + public async Task DeleteCurrentDirectory_ShouldThrowIOException_WithPath() + { + Skip.IfNot(Test.RunsOnWindows); + + IDirectoryInfo directoryInfo + = FileSystem.DirectoryInfo.New(FileSystem.Directory.GetCurrentDirectory()); + + await That(() => directoryInfo.Delete()).ThrowsExactly().Which.HasMessage( + $"The process cannot access the file '{directoryInfo.FullName}' because it is being used by another process." + ); + } +} From bc8304e66bd2d4f38ff8f6f1a92c17e574c423a9 Mon Sep 17 00:00:00 2001 From: "p.wintrich" Date: Tue, 17 Feb 2026 11:59:41 +0100 Subject: [PATCH 02/10] refactor: removed unnecessary path trimming --- .../Testably.Abstractions.Testing/Storage/InMemoryStorage.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs b/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs index 4a0aa710..dab3b2ad 100644 --- a/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs +++ b/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs @@ -1208,11 +1208,8 @@ private void ValidateHandle(string fullPath, string? errorPath = null) return; } - string path = fullPath.TrimEnd(_fileSystem.Path.DirectorySeparatorChar) - .TrimEnd(_fileSystem.Path.AltDirectorySeparatorChar); - if (_fileSystem.Directory.GetCurrentDirectory().StartsWith( - path, _fileSystem.Execute.StringComparisonMode + fullPath, _fileSystem.Execute.StringComparisonMode )) { throw string.IsNullOrEmpty(errorPath) From 96de97a023a692b222d9ef939d0907324b5f44fa Mon Sep 17 00:00:00 2001 From: "p.wintrich" Date: Tue, 17 Feb 2026 12:33:09 +0100 Subject: [PATCH 03/10] test: moved tests from RemoveCurrentDirectoryTests and added parent dir deletion --- .../FileSystem/Directory/DeleteTests.cs | 36 +++++++++++++++++++ .../FileSystem/Directory/MoveTests.cs | 35 ++++++++++++++++++ .../Directory/RemoveCurrentDirectoryTests.cs | 33 ----------------- .../FileSystem/DirectoryInfo/DeleteTests.cs | 34 ++++++++++++++++++ .../FileSystem/DirectoryInfo/MoveToTests.cs | 35 ++++++++++++++++++ .../RemoveCurrentDirectoryTests.cs | 33 ----------------- 6 files changed, 140 insertions(+), 66 deletions(-) delete mode 100644 Tests/Testably.Abstractions.Tests/FileSystem/Directory/RemoveCurrentDirectoryTests.cs delete mode 100644 Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/RemoveCurrentDirectoryTests.cs diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs index 2421ef42..782b948b 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs @@ -306,4 +306,40 @@ await That(Act).Throws() : $"'{FileSystem.Path.Combine(BasePath, path)}'"); await That(FileSystem.Directory.Exists(path)).IsTrue(); } + + [Theory] + [AutoData] + [InlineData(null)] + public async Task Delete_CurrentDirectory_ShouldThrowIOException_OnWindows(string? nested) + { + Skip.IfNot(Test.RunsOnWindows); + + // Arrange + string directory = FileSystem.Directory.GetCurrentDirectory(); + + if (nested != null) + { + string nestedDirectory = FileSystem.Path.Combine(directory, nested); + FileSystem.Directory.CreateDirectory(nestedDirectory); + FileSystem.Directory.SetCurrentDirectory(nestedDirectory); + } + + // Act + void Act() + { + FileSystem.Directory.Delete(directory); + } + + // Assert + if (Test.RunsOnWindows) + { + await That(Act).ThrowsExactly().Which.HasMessage( + $"The process cannot access the file '{directory}' because it is being used by another process." + ); + } + else + { + await That(Act).DoesNotThrow(); + } + } } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs index eff07ff9..47426e70 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs @@ -268,4 +268,39 @@ await That( SearchOption.AllDirectories)) .HasSingle(); } + + [Theory] + [AutoData] + [InlineData(null)] + public async Task Move_CurrentDirectory_ShouldThrowIOException_OnWindows(string? nested) + { + // Arrange + string directory = FileSystem.Directory.GetCurrentDirectory(); + + if (nested != null) + { + string nestedDirectory = FileSystem.Path.Combine(directory, nested); + FileSystem.Directory.CreateDirectory(nestedDirectory); + FileSystem.Directory.SetCurrentDirectory(nestedDirectory); + } + + // Act + void Act() + { + FileSystem.Directory.Move(directory, "new"); + } + + // Assert + if (Test.RunsOnWindows) + { + await That(Act) + .ThrowsExactly().Which.HasMessage( + "The process cannot access the file because it is being used by another process." + ); + } + else + { + await That(Act).DoesNotThrow(); + } + } } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/RemoveCurrentDirectoryTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/RemoveCurrentDirectoryTests.cs deleted file mode 100644 index 6a2374fd..00000000 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/RemoveCurrentDirectoryTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.IO; - -namespace Testably.Abstractions.Tests.FileSystem.Directory; - -[FileSystemTests] -public partial class RemoveCurrentDirectoryTests -{ - [Fact] - public async Task MoveCurrentDirectory_ShouldThrowIOException_WithoutPath() - { - Skip.IfNot(Test.RunsOnWindows); - - string directory = FileSystem.Directory.GetCurrentDirectory(); - - await That(() => FileSystem.Directory.Move(directory, "new")).ThrowsExactly() - .Which.HasMessage( - "The process cannot access the file because it is being used by another process." - ); - } - - [Fact] - public async Task DeleteCurrentDirectory_ShouldThrowIOException_WithPath() - { - Skip.IfNot(Test.RunsOnWindows); - - string directory = FileSystem.Directory.GetCurrentDirectory(); - - await That(() => FileSystem.Directory.Delete(directory)).ThrowsExactly().Which - .HasMessage( - $"The process cannot access the file '{directory}' because it is being used by another process." - ); - } -} diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs index 0d2d8ee8..5686b9a6 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs @@ -143,4 +143,38 @@ await That(Act).Throws() await That(sut.Exists).IsTrue(); await That(FileSystem.Directory.Exists(sut.FullName)).IsTrue(); } + + [Theory] + [AutoData] + [InlineData(null)] + public async Task Delete_CurrentDirectory_ShouldThrowIOException_OnWindows(string? nested) + { + // Arrange + string directory = FileSystem.Directory.GetCurrentDirectory(); + + if (nested != null) + { + string nestedDirectory = FileSystem.Path.Combine(directory, nested); + FileSystem.Directory.CreateDirectory(nestedDirectory); + FileSystem.Directory.SetCurrentDirectory(nestedDirectory); + } + + // Act + void Act() + { + FileSystem.DirectoryInfo.New(directory).Delete(); + } + + // Assert + if (Test.RunsOnWindows) + { + await That(Act).ThrowsExactly().Which.HasMessage( + $"The process cannot access the file '{directory}' because it is being used by another process." + ); + } + else + { + await That(Act).DoesNotThrow(); + } + } } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs index 0fec7ca3..06d9be14 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs @@ -169,4 +169,39 @@ await That( SearchOption.AllDirectories)) .HasSingle(); } + + [Theory] + [AutoData] + [InlineData(null)] + public async Task MoveTo_CurrentDirectory_ShouldThrowIOException_OnWindows(string? nested) + { + // Arrange + string directory = FileSystem.Directory.GetCurrentDirectory(); + + if (nested != null) + { + string nestedDirectory = FileSystem.Path.Combine(directory, nested); + FileSystem.Directory.CreateDirectory(nestedDirectory); + FileSystem.Directory.SetCurrentDirectory(nestedDirectory); + } + + // Act + void Act() + { + FileSystem.DirectoryInfo.New(directory).MoveTo("new"); + } + + // Assert + if (Test.RunsOnWindows) + { + await That(Act) + .ThrowsExactly().Which.HasMessage( + "The process cannot access the file because it is being used by another process." + ); + } + else + { + await That(Act).DoesNotThrow(); + } + } } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/RemoveCurrentDirectoryTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/RemoveCurrentDirectoryTests.cs deleted file mode 100644 index d2a8c382..00000000 --- a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/RemoveCurrentDirectoryTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.IO; - -namespace Testably.Abstractions.Tests.FileSystem.DirectoryInfo; - -[FileSystemTests] -public partial class RemoveCurrentDirectoryTests -{ - [Fact] - public async Task MoveCurrentDirectory_ShouldThrowIOException_WithoutPath() - { - Skip.IfNot(Test.RunsOnWindows); - - IDirectoryInfo directoryInfo - = FileSystem.DirectoryInfo.New(FileSystem.Directory.GetCurrentDirectory()); - - await That(() => directoryInfo.MoveTo("new")).ThrowsExactly().Which.HasMessage( - "The process cannot access the file because it is being used by another process." - ); - } - - [Fact] - public async Task DeleteCurrentDirectory_ShouldThrowIOException_WithPath() - { - Skip.IfNot(Test.RunsOnWindows); - - IDirectoryInfo directoryInfo - = FileSystem.DirectoryInfo.New(FileSystem.Directory.GetCurrentDirectory()); - - await That(() => directoryInfo.Delete()).ThrowsExactly().Which.HasMessage( - $"The process cannot access the file '{directoryInfo.FullName}' because it is being used by another process." - ); - } -} From dedb6875c5e1e700a48aca2a4325442894ef6e95 Mon Sep 17 00:00:00 2001 From: "p.wintrich" Date: Tue, 17 Feb 2026 14:18:00 +0100 Subject: [PATCH 04/10] test: fixed move test moving directory into itself --- .../FileSystem/Directory/MoveTests.cs | 3 ++- .../FileSystem/DirectoryInfo/MoveToTests.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs index 47426e70..80769db4 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs @@ -276,6 +276,7 @@ public async Task Move_CurrentDirectory_ShouldThrowIOException_OnWindows(string? { // Arrange string directory = FileSystem.Directory.GetCurrentDirectory(); + string newPath = FileSystem.Path.GetFullPath("../new"); if (nested != null) { @@ -287,7 +288,7 @@ public async Task Move_CurrentDirectory_ShouldThrowIOException_OnWindows(string? // Act void Act() { - FileSystem.Directory.Move(directory, "new"); + FileSystem.Directory.Move(directory, newPath); } // Assert diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs index 06d9be14..c9b8b35b 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs @@ -177,6 +177,7 @@ public async Task MoveTo_CurrentDirectory_ShouldThrowIOException_OnWindows(strin { // Arrange string directory = FileSystem.Directory.GetCurrentDirectory(); + string newPath = FileSystem.Path.GetFullPath("../new"); if (nested != null) { @@ -188,7 +189,7 @@ public async Task MoveTo_CurrentDirectory_ShouldThrowIOException_OnWindows(strin // Act void Act() { - FileSystem.DirectoryInfo.New(directory).MoveTo("new"); + FileSystem.DirectoryInfo.New(directory).MoveTo(newPath); } // Assert From e28636bd4f648b3c7f9d52c4007fac108e89c2af Mon Sep 17 00:00:00 2001 From: "p.wintrich" Date: Tue, 17 Feb 2026 15:32:55 +0100 Subject: [PATCH 05/10] test: fixed delete tests because delete wasn't recursive --- .../FileSystem/Directory/DeleteTests.cs | 2 +- .../FileSystem/DirectoryInfo/DeleteTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs index 782b948b..0383b2fb 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs @@ -327,7 +327,7 @@ public async Task Delete_CurrentDirectory_ShouldThrowIOException_OnWindows(strin // Act void Act() { - FileSystem.Directory.Delete(directory); + FileSystem.Directory.Delete(directory, true); } // Assert diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs index 5686b9a6..56a13a44 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs @@ -162,7 +162,7 @@ public async Task Delete_CurrentDirectory_ShouldThrowIOException_OnWindows(strin // Act void Act() { - FileSystem.DirectoryInfo.New(directory).Delete(); + FileSystem.DirectoryInfo.New(directory).Delete(true); } // Assert From f9507f464edac198a08b393b9277474488dcd556 Mon Sep 17 00:00:00 2001 From: "p.wintrich" Date: Wed, 18 Feb 2026 08:56:07 +0100 Subject: [PATCH 06/10] fix: fixed handle checks in InMemoryStorage --- .../Helpers/ExceptionFactory.cs | 8 +++++ .../Storage/InMemoryStorage.cs | 36 ++++++++++++++----- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/Source/Testably.Abstractions.Testing/Helpers/ExceptionFactory.cs b/Source/Testably.Abstractions.Testing/Helpers/ExceptionFactory.cs index 364abeee..6f027030 100644 --- a/Source/Testably.Abstractions.Testing/Helpers/ExceptionFactory.cs +++ b/Source/Testably.Abstractions.Testing/Helpers/ExceptionFactory.cs @@ -313,6 +313,14 @@ internal static UnauthorizedAccessException AccessDenied(string path) #endif }; + internal static IOException IOAccessDenied(string path) + => new($"Access to the path '{path}' is denied.") + { +#if FEATURE_EXCEPTION_HRESULT + HResult = -2147024891, +#endif + }; + internal static PlatformNotSupportedException UnixFileModeNotSupportedOnThisPlatform() => new("Unix file modes are not supported on this platform.") { diff --git a/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs b/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs index dab3b2ad..9441f37f 100644 --- a/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs +++ b/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs @@ -145,7 +145,7 @@ public bool DeleteContainer( ValidateContainerType(container.Type, expectedType, _fileSystem.Execute, location); - ValidateHandle(location.FullPath, location.FullPath); + ValidateDeleteHandle(location.FullPath); if (container.Type == FileSystemTypes.Directory) { @@ -994,7 +994,7 @@ private bool IncludeItemInEnumeration( return null; } - ValidateHandle(source.FullPath); + ValidateMoveHandle(source.FullPath); if (container.Type == FileSystemTypes.Directory && source.FullPath.Equals(destination.FullPath, _fileSystem.Execute.IsNetFramework @@ -1201,20 +1201,38 @@ private static void ValidateContainerType( } } - private void ValidateHandle(string fullPath, string? errorPath = null) + private void ValidateDeleteHandle(string fullPath) { if (!_fileSystem.Execute.IsWindows) { return; } + + string? currentDirectory = _fileSystem.Directory.GetCurrentDirectory().NormalizePath(_fileSystem); + + if (currentDirectory.StartsWith(fullPath, _fileSystem.Execute.StringComparisonMode)) + { + throw ExceptionFactory.FileSharingViolation(currentDirectory); + } + } + + private void ValidateMoveHandle(string fullPath) + { + if (!_fileSystem.Execute.IsWindows) + { + return; + } + + string? currentDirectory = _fileSystem.Directory.GetCurrentDirectory().NormalizePath(_fileSystem); + + if (currentDirectory.Equals(fullPath, _fileSystem.Execute.StringComparisonMode)) + { + throw ExceptionFactory.FileSharingViolation(); + } - if (_fileSystem.Directory.GetCurrentDirectory().StartsWith( - fullPath, _fileSystem.Execute.StringComparisonMode - )) + if (currentDirectory.StartsWith(fullPath, _fileSystem.Execute.StringComparisonMode)) { - throw string.IsNullOrEmpty(errorPath) - ? ExceptionFactory.FileSharingViolation() - : ExceptionFactory.FileSharingViolation(errorPath); + throw ExceptionFactory.IOAccessDenied(fullPath); } } From 7252de541512512efeb7c623d4d92027f967753c Mon Sep 17 00:00:00 2001 From: "p.wintrich" Date: Wed, 18 Feb 2026 09:29:45 +0100 Subject: [PATCH 07/10] test: fixed test cases for current dir modification --- .../FileSystem/Directory/DeleteTests.cs | 29 ++++++---- .../FileSystem/Directory/MoveTests.cs | 41 ++++++++++---- .../FileSystem/DirectoryInfo/DeleteTests.cs | 27 +++++++--- .../FileSystem/DirectoryInfo/MoveToTests.cs | 54 +++++++++++++++---- 4 files changed, 115 insertions(+), 36 deletions(-) diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs index 0383b2fb..9c147360 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs @@ -312,16 +312,16 @@ await That(Act).Throws() [InlineData(null)] public async Task Delete_CurrentDirectory_ShouldThrowIOException_OnWindows(string? nested) { - Skip.IfNot(Test.RunsOnWindows); - // Arrange string directory = FileSystem.Directory.GetCurrentDirectory(); + string expectedExceptionDirectory = directory; if (nested != null) { string nestedDirectory = FileSystem.Path.Combine(directory, nested); FileSystem.Directory.CreateDirectory(nestedDirectory); FileSystem.Directory.SetCurrentDirectory(nestedDirectory); + expectedExceptionDirectory = nestedDirectory; } // Act @@ -330,16 +330,27 @@ void Act() FileSystem.Directory.Delete(directory, true); } - // Assert - if (Test.RunsOnWindows) + try { - await That(Act).ThrowsExactly().Which.HasMessage( - $"The process cannot access the file '{directory}' because it is being used by another process." - ); + // Assert + if (Test.RunsOnWindows) + { + await That(Act).ThrowsExactly().Which.HasMessage( + $"The process cannot access the file '*{expectedExceptionDirectory}' because it is being used by another process." + ).AsWildcard(); + } + else + { + await That(Act).DoesNotThrow(); + } } - else + finally { - await That(Act).DoesNotThrow(); + if (Test.RunsOnWindows) + { + // Cleanup + FileSystem.Directory.SetCurrentDirectory(BasePath); + } } } } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs index 80769db4..f3137d49 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs @@ -269,21 +269,44 @@ await That( .HasSingle(); } - [Theory] - [AutoData] - [InlineData(null)] - public async Task Move_CurrentDirectory_ShouldThrowIOException_OnWindows(string? nested) + [Fact] + public async Task Move_CurrentDirectory_ShouldThrowIOException_OnWindows() { // Arrange string directory = FileSystem.Directory.GetCurrentDirectory(); string newPath = FileSystem.Path.GetFullPath("../new"); - if (nested != null) + // Act + void Act() + { + FileSystem.Directory.Move(directory, newPath); + } + + // Assert + if (Test.RunsOnWindows) + { + await That(Act) + .ThrowsExactly().Which.HasMessage( + "The process cannot access the file because it is being used by another process." + ); + } + else { - string nestedDirectory = FileSystem.Path.Combine(directory, nested); - FileSystem.Directory.CreateDirectory(nestedDirectory); - FileSystem.Directory.SetCurrentDirectory(nestedDirectory); + await That(Act).DoesNotThrow(); } + } + + [Theory] + [AutoData] + public async Task Move_NestedCurrentDirectory_ShouldThrowIOException_OnWindows(string nested) + { + // Arrange + string directory = FileSystem.Directory.GetCurrentDirectory(); + string newPath = FileSystem.Path.GetFullPath("../new"); + + string nestedDirectory = FileSystem.Path.Combine(directory, nested); + FileSystem.Directory.CreateDirectory(nestedDirectory); + FileSystem.Directory.SetCurrentDirectory(nestedDirectory); // Act void Act() @@ -296,7 +319,7 @@ void Act() { await That(Act) .ThrowsExactly().Which.HasMessage( - "The process cannot access the file because it is being used by another process." + $"Access to the path '{directory}' is denied." ); } else diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs index 56a13a44..531547a1 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs @@ -151,12 +151,14 @@ public async Task Delete_CurrentDirectory_ShouldThrowIOException_OnWindows(strin { // Arrange string directory = FileSystem.Directory.GetCurrentDirectory(); + string expectedExceptionDirectory = directory; if (nested != null) { string nestedDirectory = FileSystem.Path.Combine(directory, nested); FileSystem.Directory.CreateDirectory(nestedDirectory); FileSystem.Directory.SetCurrentDirectory(nestedDirectory); + expectedExceptionDirectory = nestedDirectory; } // Act @@ -165,16 +167,27 @@ void Act() FileSystem.DirectoryInfo.New(directory).Delete(true); } - // Assert - if (Test.RunsOnWindows) + try { - await That(Act).ThrowsExactly().Which.HasMessage( - $"The process cannot access the file '{directory}' because it is being used by another process." - ); + // Assert + if (Test.RunsOnWindows) + { + await That(Act).ThrowsExactly().Which.HasMessage( + $"The process cannot access the file '*{expectedExceptionDirectory}' because it is being used by another process." + ).AsWildcard(); + } + else + { + await That(Act).DoesNotThrow(); + } } - else + finally { - await That(Act).DoesNotThrow(); + if (Test.RunsOnWindows) + { + // Cleanup + FileSystem.Directory.SetCurrentDirectory(BasePath); + } } } } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs index c9b8b35b..9fd56aa1 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs @@ -170,22 +170,13 @@ await That( .HasSingle(); } - [Theory] - [AutoData] - [InlineData(null)] - public async Task MoveTo_CurrentDirectory_ShouldThrowIOException_OnWindows(string? nested) + [Fact] + public async Task MoveTo_CurrentDirectory_ShouldThrowIOException_OnWindows() { // Arrange string directory = FileSystem.Directory.GetCurrentDirectory(); string newPath = FileSystem.Path.GetFullPath("../new"); - if (nested != null) - { - string nestedDirectory = FileSystem.Path.Combine(directory, nested); - FileSystem.Directory.CreateDirectory(nestedDirectory); - FileSystem.Directory.SetCurrentDirectory(nestedDirectory); - } - // Act void Act() { @@ -205,4 +196,45 @@ await That(Act) await That(Act).DoesNotThrow(); } } + + [Theory] + [AutoData] + public async Task MoveTo_NestedCurrentDirectory_ShouldThrowIOException_OnWindows(string nested) + { + // Arrange + string directory = FileSystem.Directory.GetCurrentDirectory(); + string newPath = FileSystem.Path.GetFullPath("../new"); + + string nestedDirectory = FileSystem.Path.Combine(directory, nested); + FileSystem.Directory.CreateDirectory(nestedDirectory); + FileSystem.Directory.SetCurrentDirectory(nestedDirectory); + + // Act + void Act() + { + FileSystem.DirectoryInfo.New(directory).MoveTo(newPath); + } + + try + { + // Assert + if (Test.RunsOnWindows) + { + await That(Act).ThrowsExactly().Which + .HasMessage($"Access to the path '*{directory}' is denied.").AsWildcard(); + } + else + { + await That(Act).DoesNotThrow(); + } + } + finally + { + if (Test.RunsOnWindows) + { + // Cleanup + FileSystem.Directory.SetCurrentDirectory(BasePath); + } + } + } } From b3301854b1a8e67fd26d32cfaa6bd7e528dbcd5b Mon Sep 17 00:00:00 2001 From: "p.wintrich" Date: Wed, 18 Feb 2026 09:38:11 +0100 Subject: [PATCH 08/10] test: applied suggestion of OnlyIf to current dir modification tests --- .../FileSystem/Directory/DeleteTests.cs | 10 ++----- .../FileSystem/Directory/MoveTests.cs | 30 ++++++++----------- .../FileSystem/DirectoryInfo/DeleteTests.cs | 10 ++----- .../FileSystem/DirectoryInfo/MoveToTests.cs | 25 ++++------------ 4 files changed, 21 insertions(+), 54 deletions(-) diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs index 9c147360..4c9f1fb3 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs @@ -333,16 +333,10 @@ void Act() try { // Assert - if (Test.RunsOnWindows) - { - await That(Act).ThrowsExactly().Which.HasMessage( + await That(Act).ThrowsExactly().OnlyIf(Test.RunsOnWindows).Which + .HasMessage( $"The process cannot access the file '*{expectedExceptionDirectory}' because it is being used by another process." ).AsWildcard(); - } - else - { - await That(Act).DoesNotThrow(); - } } finally { diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs index f3137d49..953b9fa3 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs @@ -283,17 +283,9 @@ void Act() } // Assert - if (Test.RunsOnWindows) - { - await That(Act) - .ThrowsExactly().Which.HasMessage( - "The process cannot access the file because it is being used by another process." - ); - } - else - { - await That(Act).DoesNotThrow(); - } + await That(Act).ThrowsExactly().OnlyIf(Test.RunsOnWindows).Which.HasMessage( + "The process cannot access the file because it is being used by another process." + ); } [Theory] @@ -315,16 +307,18 @@ void Act() } // Assert - if (Test.RunsOnWindows) + try { - await That(Act) - .ThrowsExactly().Which.HasMessage( - $"Access to the path '{directory}' is denied." - ); + await That(Act).ThrowsExactly().OnlyIf(Test.RunsOnWindows).Which + .HasMessage($"Access to the path '{directory}' is denied."); } - else + finally { - await That(Act).DoesNotThrow(); + if (Test.RunsOnWindows) + { + // Cleanup + FileSystem.Directory.SetCurrentDirectory(BasePath); + } } } } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs index 531547a1..8840b558 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs @@ -170,16 +170,10 @@ void Act() try { // Assert - if (Test.RunsOnWindows) - { - await That(Act).ThrowsExactly().Which.HasMessage( + await That(Act).ThrowsExactly().OnlyIf(Test.RunsOnWindows).Which + .HasMessage( $"The process cannot access the file '*{expectedExceptionDirectory}' because it is being used by another process." ).AsWildcard(); - } - else - { - await That(Act).DoesNotThrow(); - } } finally { diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs index 9fd56aa1..bc83e638 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs @@ -184,17 +184,9 @@ void Act() } // Assert - if (Test.RunsOnWindows) - { - await That(Act) - .ThrowsExactly().Which.HasMessage( - "The process cannot access the file because it is being used by another process." - ); - } - else - { - await That(Act).DoesNotThrow(); - } + await That(Act).ThrowsExactly().OnlyIf(Test.RunsOnWindows).Which.HasMessage( + "The process cannot access the file because it is being used by another process." + ); } [Theory] @@ -218,15 +210,8 @@ void Act() try { // Assert - if (Test.RunsOnWindows) - { - await That(Act).ThrowsExactly().Which - .HasMessage($"Access to the path '*{directory}' is denied.").AsWildcard(); - } - else - { - await That(Act).DoesNotThrow(); - } + await That(Act).ThrowsExactly().OnlyIf(Test.RunsOnWindows).Which + .HasMessage($"Access to the path '*{directory}' is denied.").AsWildcard(); } finally { From 77335839f3f6b6b113a56fe705075f00d2ae3245 Mon Sep 17 00:00:00 2001 From: "p.wintrich" Date: Thu, 19 Feb 2026 08:34:16 +0100 Subject: [PATCH 09/10] fix: fixed handle check for modifiying directories similar to the current directory --- .../Storage/InMemoryStorage.cs | 36 ++++++++++++------- .../FileSystem/Directory/DeleteTests.cs | 26 ++++++++++++++ .../FileSystem/Directory/MoveTests.cs | 27 ++++++++++++++ .../FileSystem/DirectoryInfo/DeleteTests.cs | 26 ++++++++++++++ .../FileSystem/DirectoryInfo/MoveToTests.cs | 27 ++++++++++++++ 5 files changed, 129 insertions(+), 13 deletions(-) diff --git a/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs b/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs index 9441f37f..0d87ff04 100644 --- a/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs +++ b/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs @@ -145,7 +145,7 @@ public bool DeleteContainer( ValidateContainerType(container.Type, expectedType, _fileSystem.Execute, location); - ValidateDeleteHandle(location.FullPath); + ValidateDeleteHandle(container, location.FullPath); if (container.Type == FileSystemTypes.Directory) { @@ -993,8 +993,8 @@ private bool IncludeItemInEnumeration( { return null; } - - ValidateMoveHandle(source.FullPath); + + ValidateMoveHandle(container, source.FullPath); if (container.Type == FileSystemTypes.Directory && source.FullPath.Equals(destination.FullPath, _fileSystem.Execute.IsNetFramework @@ -1201,36 +1201,46 @@ private static void ValidateContainerType( } } - private void ValidateDeleteHandle(string fullPath) + private void ValidateDeleteHandle(IStorageContainer container, string fullPath) { - if (!_fileSystem.Execute.IsWindows) + if (!_fileSystem.Execute.IsWindows || container.Type != FileSystemTypes.Directory) { return; } - - string? currentDirectory = _fileSystem.Directory.GetCurrentDirectory().NormalizePath(_fileSystem); - if (currentDirectory.StartsWith(fullPath, _fileSystem.Execute.StringComparisonMode)) + string? currentDirectory + = _fileSystem.Directory.GetCurrentDirectory().NormalizePath(_fileSystem) + + _fileSystem.Path.DirectorySeparatorChar; + + string fullPathWithSeparator = fullPath + _fileSystem.Path.DirectorySeparatorChar; + + if (currentDirectory.StartsWith( + fullPathWithSeparator, _fileSystem.Execute.StringComparisonMode + )) { throw ExceptionFactory.FileSharingViolation(currentDirectory); } } - private void ValidateMoveHandle(string fullPath) + private void ValidateMoveHandle(IStorageContainer container, string fullPath) { - if (!_fileSystem.Execute.IsWindows) + if (!_fileSystem.Execute.IsWindows || container.Type != FileSystemTypes.Directory) { return; } - string? currentDirectory = _fileSystem.Directory.GetCurrentDirectory().NormalizePath(_fileSystem); + string? currentDirectory + = _fileSystem.Directory.GetCurrentDirectory().NormalizePath(_fileSystem) + + _fileSystem.Path.DirectorySeparatorChar; + + string fullPathWithSeparator = fullPath + _fileSystem.Path.DirectorySeparatorChar; - if (currentDirectory.Equals(fullPath, _fileSystem.Execute.StringComparisonMode)) + if (currentDirectory.Equals(fullPathWithSeparator, _fileSystem.Execute.StringComparisonMode)) { throw ExceptionFactory.FileSharingViolation(); } - if (currentDirectory.StartsWith(fullPath, _fileSystem.Execute.StringComparisonMode)) + if (currentDirectory.StartsWith(fullPathWithSeparator, _fileSystem.Execute.StringComparisonMode)) { throw ExceptionFactory.IOAccessDenied(fullPath); } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs index 4c9f1fb3..064194dd 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/DeleteTests.cs @@ -347,4 +347,30 @@ await That(Act).ThrowsExactly().OnlyIf(Test.RunsOnWindows).Which } } } + + [Theory] + [InlineData("next")] + [InlineData("next", "sub")] + public async Task Delete_DirNextToCurrentDirectory_ShouldNotThrow(params string[] paths) + { + // Arrange + string directory = FileSystem.Directory.GetCurrentDirectory(); + // Intended missing separator, we want to test that the handle does not affect similar paths + string nextTo = directory + FileSystem.Path.Combine(paths); + + FileSystem.Directory.CreateDirectory(nextTo); + FileSystem.Directory.SetCurrentDirectory(nextTo); + + // Act + void Act() + { + FileSystem.Directory.Delete(directory, true); + } + + // Assert + await That(Act).DoesNotThrow(); + + await That(FileSystem.Directory.Exists(directory)).IsFalse(); + await That(FileSystem.Directory.Exists(nextTo)).IsTrue(); + } } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs index 953b9fa3..19b62df4 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/Directory/MoveTests.cs @@ -321,4 +321,31 @@ await That(Act).ThrowsExactly().OnlyIf(Test.RunsOnWindows).Which } } } + + [Theory] + [InlineData("next")] + [InlineData("next", "sub")] + public async Task Move_DirNextToCurrentDirectory_ShouldNotThrow(params string[] paths) + { + // Arrange + string directory = FileSystem.Directory.GetCurrentDirectory(); + // Intended missing separator, we want to test that the handle does not affect similar paths + string nextTo = directory + FileSystem.Path.Combine(paths); + string moveTarget = FileSystem.Path.Combine(nextTo, "move-target"); + + FileSystem.Directory.CreateDirectory(nextTo); + FileSystem.Directory.SetCurrentDirectory(nextTo); + + // Act + void Act() + { + FileSystem.Directory.Move(directory, moveTarget); + } + + // Assert + await That(Act).DoesNotThrow(); + + await That(FileSystem.Directory.Exists(directory)).IsFalse(); + await That(FileSystem.Directory.Exists(nextTo)).IsTrue(); + } } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs index 8840b558..481fc28c 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/DeleteTests.cs @@ -184,4 +184,30 @@ await That(Act).ThrowsExactly().OnlyIf(Test.RunsOnWindows).Which } } } + + [Theory] + [InlineData("next")] + [InlineData("next", "sub")] + public async Task Delete_DirNextToCurrentDirectory_ShouldNotThrow(params string[] paths) + { + // Arrange + string directory = FileSystem.Directory.GetCurrentDirectory(); + // Intended missing separator, we want to test that the handle does not affect similar paths + string nextTo = directory + FileSystem.Path.Combine(paths); + + FileSystem.Directory.CreateDirectory(nextTo); + FileSystem.Directory.SetCurrentDirectory(nextTo); + + // Act + void Act() + { + FileSystem.DirectoryInfo.New(directory).Delete(true); + } + + // Assert + await That(Act).DoesNotThrow(); + + await That(FileSystem.Directory.Exists(directory)).IsFalse(); + await That(FileSystem.Directory.Exists(nextTo)).IsTrue(); + } } diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs index bc83e638..e9daa460 100644 --- a/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs +++ b/Tests/Testably.Abstractions.Tests/FileSystem/DirectoryInfo/MoveToTests.cs @@ -222,4 +222,31 @@ await That(Act).ThrowsExactly().OnlyIf(Test.RunsOnWindows).Which } } } + + [Theory] + [InlineData("next")] + [InlineData("next", "sub")] + public async Task Move_DirNextToCurrentDirectory_ShouldNotThrow(params string[] paths) + { + // Arrange + string directory = FileSystem.Directory.GetCurrentDirectory(); + // Intended missing separator, we want to test that the handle does not affect similar paths + string nextTo = directory + FileSystem.Path.Combine(paths); + string moveTarget = FileSystem.Path.Combine(nextTo, "move-target"); + + FileSystem.Directory.CreateDirectory(nextTo); + FileSystem.Directory.SetCurrentDirectory(nextTo); + + // Act + void Act() + { + FileSystem.DirectoryInfo.New(directory).MoveTo(moveTarget); + } + + // Assert + await That(Act).DoesNotThrow(); + + await That(FileSystem.Directory.Exists(directory)).IsFalse(); + await That(FileSystem.Directory.Exists(nextTo)).IsTrue(); + } } From 173eb2e1a65552271676441a82b631d588917db6 Mon Sep 17 00:00:00 2001 From: "p.wintrich" Date: Thu, 19 Feb 2026 10:45:51 +0100 Subject: [PATCH 10/10] fix: fixed trailing separator in file sharing exception --- .../Storage/InMemoryStorage.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs b/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs index 0d87ff04..d62ed253 100644 --- a/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs +++ b/Source/Testably.Abstractions.Testing/Storage/InMemoryStorage.cs @@ -1209,12 +1209,14 @@ private void ValidateDeleteHandle(IStorageContainer container, string fullPath) } string? currentDirectory - = _fileSystem.Directory.GetCurrentDirectory().NormalizePath(_fileSystem) - + _fileSystem.Path.DirectorySeparatorChar; + = _fileSystem.Directory.GetCurrentDirectory().NormalizePath(_fileSystem); + + string currentDirectoryWithSeparator + = currentDirectory + _fileSystem.Path.DirectorySeparatorChar; string fullPathWithSeparator = fullPath + _fileSystem.Path.DirectorySeparatorChar; - if (currentDirectory.StartsWith( + if (currentDirectoryWithSeparator.StartsWith( fullPathWithSeparator, _fileSystem.Execute.StringComparisonMode )) {