From 2739e8533d8165af259ed96e7b00474a49a15fbf Mon Sep 17 00:00:00 2001 From: HarrisonTCodes Date: Sat, 18 Oct 2025 21:09:36 +0100 Subject: [PATCH 01/12] fix: added correct handling of file share in file stream constructor/factory --- .../MockFile.cs | 2 +- .../MockFileStreamFactory.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFile.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFile.cs index 0f9a97a48..e4d739704 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFile.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFile.cs @@ -677,7 +677,7 @@ private FileSystemStream OpenInternal( } mockFileDataAccessor.AdjustTimes(mockFileData, timeAdjustments); - return new MockFileStream(mockFileDataAccessor, path, mode, access, options); + return new MockFileStream(mockFileDataAccessor, path, mode, access, FileShare.Read, options); } /// diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStreamFactory.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStreamFactory.cs index 9cd34f7ae..5cfb2a5a0 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStreamFactory.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStreamFactory.cs @@ -41,25 +41,25 @@ public FileSystemStream New(string path, FileMode mode, FileAccess access) /// public FileSystemStream New(string path, FileMode mode, FileAccess access, FileShare share) - => new MockFileStream(mockFileSystem, path, mode, access); + => new MockFileStream(mockFileSystem, path, mode, access, share); /// public FileSystemStream New(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) - => new MockFileStream(mockFileSystem, path, mode, access); + => new MockFileStream(mockFileSystem, path, mode, access, share); /// public FileSystemStream New(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync) - => new MockFileStream(mockFileSystem, path, mode, access); + => new MockFileStream(mockFileSystem, path, mode, access, share); /// public FileSystemStream New(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) - => new MockFileStream(mockFileSystem, path, mode, access, options); + => new MockFileStream(mockFileSystem, path, mode, access, share, options); #if FEATURE_FILESTREAM_OPTIONS /// public FileSystemStream New(string path, FileStreamOptions options) - => new MockFileStream(mockFileSystem, path, options.Mode, options.Access, options.Options); + => new MockFileStream(mockFileSystem, path, options.Mode, options.Access, options: options.Options); #endif /// From ec701cf151e98b96891cd9386a82b362d7d4e07b Mon Sep 17 00:00:00 2001 From: HarrisonTCodes Date: Sat, 18 Oct 2025 21:11:12 +0100 Subject: [PATCH 02/12] fix: added stateful tracking of unshared file streams and prevented multiple openings --- .../MockFileStream.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs index 1be2241af..dee44baad 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Runtime.Versioning; using System.Security.AccessControl; +using System.Collections.Generic; namespace System.IO.Abstractions.TestingHelpers; @@ -32,9 +33,11 @@ public NullFileSystemStream() : base(Null, ".", true) private readonly IMockFileDataAccessor mockFileDataAccessor; private readonly string path; private readonly FileAccess access = FileAccess.ReadWrite; + private readonly FileShare share = FileShare.Read; private readonly FileOptions options; private readonly MockFileData fileData; private bool disposed; + private static HashSet _fileShareNoneStreams = []; /// public MockFileStream( @@ -42,6 +45,7 @@ public MockFileStream( string path, FileMode mode, FileAccess access = FileAccess.ReadWrite, + FileShare share = FileShare.Read, FileOptions options = FileOptions.None) : base(new MemoryStream(), path == null ? null : Path.GetFullPath(path), @@ -54,6 +58,11 @@ public MockFileStream( this.path = path; this.options = options; + if (_fileShareNoneStreams.Contains(path)) + { + throw new IOException($"The process cannot access the file '{path}' because it is being used by another process."); + } + if (mockFileDataAccessor.FileExists(path)) { if (mode.Equals(FileMode.CreateNew)) @@ -97,7 +106,12 @@ public MockFileStream( mockFileDataAccessor.AddFile(path, fileData); } + if (share is FileShare.None) + { + _fileShareNoneStreams.Add(path); + } this.access = access; + this.share = share; } private static void ThrowIfInvalidModeAccess(FileMode mode, FileAccess access) @@ -144,6 +158,10 @@ protected override void Dispose(bool disposing) { return; } + if (share is FileShare.None) + { + _fileShareNoneStreams.Remove(path); + } InternalFlush(); base.Dispose(disposing); OnClose(); From 2a70ae1c126c593010590d0e3ead15710d3e5f99 Mon Sep 17 00:00:00 2001 From: HarrisonTCodes Date: Thu, 30 Oct 2025 17:27:16 +0000 Subject: [PATCH 03/12] refactor: changed fileshare none streams state to use concurrent dictionary instead of hash set --- .../MockFileStream.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs index dee44baad..951773759 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Runtime.Versioning; using System.Security.AccessControl; -using System.Collections.Generic; +using System.Collections.Concurrent; namespace System.IO.Abstractions.TestingHelpers; @@ -37,7 +37,7 @@ public NullFileSystemStream() : base(Null, ".", true) private readonly FileOptions options; private readonly MockFileData fileData; private bool disposed; - private static HashSet _fileShareNoneStreams = []; + private static ConcurrentDictionary _fileShareNoneStreams = []; /// public MockFileStream( @@ -58,7 +58,7 @@ public MockFileStream( this.path = path; this.options = options; - if (_fileShareNoneStreams.Contains(path)) + if (_fileShareNoneStreams.ContainsKey(path)) { throw new IOException($"The process cannot access the file '{path}' because it is being used by another process."); } @@ -108,7 +108,7 @@ public MockFileStream( if (share is FileShare.None) { - _fileShareNoneStreams.Add(path); + _fileShareNoneStreams.TryAdd(path, 0); } this.access = access; this.share = share; @@ -160,7 +160,7 @@ protected override void Dispose(bool disposing) } if (share is FileShare.None) { - _fileShareNoneStreams.Remove(path); + _fileShareNoneStreams.TryRemove(path, out _); } InternalFlush(); base.Dispose(disposing); From 18716e4c3b1dc022ece1205881f9ae20bcb9efbb Mon Sep 17 00:00:00 2001 From: HarrisonTCodes Date: Thu, 30 Oct 2025 17:34:29 +0000 Subject: [PATCH 04/12] refactor: used existing common exception for file-in-use error in file stream opening --- .../MockFileStream.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs index 951773759..85409cf42 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs @@ -60,7 +60,7 @@ public MockFileStream( if (_fileShareNoneStreams.ContainsKey(path)) { - throw new IOException($"The process cannot access the file '{path}' because it is being used by another process."); + throw CommonExceptions.ProcessCannotAccessFileInUse(path); } if (mockFileDataAccessor.FileExists(path)) From c6600948ef3756ffab27d04577dba39f083f22cb Mon Sep 17 00:00:00 2001 From: HarrisonTCodes Date: Thu, 30 Oct 2025 17:37:58 +0000 Subject: [PATCH 05/12] feat: added handling of failed addition of exclusive file stream to tracked state --- .../MockFileStream.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs index 85409cf42..32aa58031 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs @@ -108,7 +108,10 @@ public MockFileStream( if (share is FileShare.None) { - _fileShareNoneStreams.TryAdd(path, 0); + if (!_fileShareNoneStreams.TryAdd(path, 0)) + { + throw CommonExceptions.ProcessCannotAccessFileInUse(path); + } } this.access = access; this.share = share; From d648b5e929676ad48bfd7526d17388d05cc45b15 Mon Sep 17 00:00:00 2001 From: HarrisonTCodes Date: Fri, 31 Oct 2025 21:11:45 +0000 Subject: [PATCH 06/12] chore: explicit API acceptance test changes --- .../TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt | 2 +- .../TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt | 2 +- .../TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt | 2 +- .../TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt | 2 +- ...eIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt | 2 +- ...eIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt index 788ddb703..386e51112 100644 --- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt +++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt @@ -297,7 +297,7 @@ namespace System.IO.Abstractions.TestingHelpers [System.Serializable] public class MockFileStream : System.IO.Abstractions.FileSystemStream, System.IO.Abstractions.IFileSystemAclSupport { - public MockFileStream(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor, string path, System.IO.FileMode mode, System.IO.FileAccess access = 3, System.IO.FileOptions options = 0) { } + public MockFileStream(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor, string path, System.IO.FileMode mode, System.IO.FileAccess access = 3, System.IO.FileShare share = 1, System.IO.FileOptions options = 0) { } public override bool CanRead { get; } public override bool CanWrite { get; } public static System.IO.Abstractions.FileSystemStream Null { get; } diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt index afd2ce3c4..30e5f6341 100644 --- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt +++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt @@ -346,7 +346,7 @@ namespace System.IO.Abstractions.TestingHelpers [System.Serializable] public class MockFileStream : System.IO.Abstractions.FileSystemStream, System.IO.Abstractions.IFileSystemAclSupport { - public MockFileStream(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor, string path, System.IO.FileMode mode, System.IO.FileAccess access = 3, System.IO.FileOptions options = 0) { } + public MockFileStream(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor, string path, System.IO.FileMode mode, System.IO.FileAccess access = 3, System.IO.FileShare share = 1, System.IO.FileOptions options = 0) { } public override bool CanRead { get; } public override bool CanWrite { get; } public static System.IO.Abstractions.FileSystemStream Null { get; } diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt index 1c581cab8..a7216a7b7 100644 --- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt +++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt @@ -370,7 +370,7 @@ namespace System.IO.Abstractions.TestingHelpers [System.Serializable] public class MockFileStream : System.IO.Abstractions.FileSystemStream, System.IO.Abstractions.IFileSystemAclSupport { - public MockFileStream(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor, string path, System.IO.FileMode mode, System.IO.FileAccess access = 3, System.IO.FileOptions options = 0) { } + public MockFileStream(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor, string path, System.IO.FileMode mode, System.IO.FileAccess access = 3, System.IO.FileShare share = 1, System.IO.FileOptions options = 0) { } public override bool CanRead { get; } public override bool CanWrite { get; } public static System.IO.Abstractions.FileSystemStream Null { get; } diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt index 6c78cf677..1e94ced04 100644 --- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt +++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt @@ -384,7 +384,7 @@ namespace System.IO.Abstractions.TestingHelpers [System.Serializable] public class MockFileStream : System.IO.Abstractions.FileSystemStream, System.IO.Abstractions.IFileSystemAclSupport { - public MockFileStream(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor, string path, System.IO.FileMode mode, System.IO.FileAccess access = 3, System.IO.FileOptions options = 0) { } + public MockFileStream(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor, string path, System.IO.FileMode mode, System.IO.FileAccess access = 3, System.IO.FileShare share = 1, System.IO.FileOptions options = 0) { } public override bool CanRead { get; } public override bool CanWrite { get; } public static System.IO.Abstractions.FileSystemStream Null { get; } diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt index e0880b9f8..e12d0f471 100644 --- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt +++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt @@ -297,7 +297,7 @@ namespace System.IO.Abstractions.TestingHelpers [System.Serializable] public class MockFileStream : System.IO.Abstractions.FileSystemStream, System.IO.Abstractions.IFileSystemAclSupport { - public MockFileStream(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor, string path, System.IO.FileMode mode, System.IO.FileAccess access = 3, System.IO.FileOptions options = 0) { } + public MockFileStream(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor, string path, System.IO.FileMode mode, System.IO.FileAccess access = 3, System.IO.FileShare share = 1, System.IO.FileOptions options = 0) { } public override bool CanRead { get; } public override bool CanWrite { get; } public static System.IO.Abstractions.FileSystemStream Null { get; } diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt index 80f876c0c..bead7ec0e 100644 --- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt +++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt @@ -323,7 +323,7 @@ namespace System.IO.Abstractions.TestingHelpers [System.Serializable] public class MockFileStream : System.IO.Abstractions.FileSystemStream, System.IO.Abstractions.IFileSystemAclSupport { - public MockFileStream(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor, string path, System.IO.FileMode mode, System.IO.FileAccess access = 3, System.IO.FileOptions options = 0) { } + public MockFileStream(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor, string path, System.IO.FileMode mode, System.IO.FileAccess access = 3, System.IO.FileShare share = 1, System.IO.FileOptions options = 0) { } public override bool CanRead { get; } public override bool CanWrite { get; } public static System.IO.Abstractions.FileSystemStream Null { get; } From 9952eabea0d21fdf5aaeb32e60113ed22ae3ae78 Mon Sep 17 00:00:00 2001 From: HarrisonTCodes Date: Fri, 31 Oct 2025 22:00:52 +0000 Subject: [PATCH 07/12] test: added exclusive mock file stream handling unit tests --- .../MockFileStreamTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs index b82566d9a..f973c1732 100644 --- a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs +++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs @@ -390,4 +390,26 @@ async Task Act() => await source.CopyToAsync(destination); await That(Act).Throws(); } + + [Test] + public async Task MockFileStream_WhenExclusiveStreamOpen_ShouldThrowIOException() + { + var fileSystem = new MockFileSystem(); + fileSystem.File.WriteAllText("foo.txt", ""); + using (new MockFileStream(fileSystem, "foo.txt", FileMode.Open, FileAccess.Read, FileShare.None)) + { + await That(() => new MockFileStream(fileSystem, "foo.txt", FileMode.Open, FileAccess.Read)).Throws(); + } + } + + [Test] + public async Task MockFileStream_WhenExclusiveStreamClosed_ShouldNotThrow() + { + var fileSystem = new MockFileSystem(); + fileSystem.File.WriteAllText("foo.txt", ""); + var stream = new MockFileStream(fileSystem, "foo.txt", FileMode.Open, FileAccess.Read, FileShare.None); + stream.Dispose(); + + await That(() => new MockFileStream(fileSystem, "foo.txt", FileMode.Open, FileAccess.Read)).DoesNotThrow(); + } } \ No newline at end of file From 4e724f5f92fa0fa799fc4366a83e50d5a4428cc3 Mon Sep 17 00:00:00 2001 From: HarrisonTCodes Date: Fri, 31 Oct 2025 22:46:06 +0000 Subject: [PATCH 08/12] feat: added path normalization to mock file stream --- .../MockFileStream.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs index 32aa58031..77553b851 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs @@ -55,6 +55,7 @@ public MockFileStream( ThrowIfInvalidModeAccess(mode, access); this.mockFileDataAccessor = mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor)); + path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar).TrimSlashes(); this.path = path; this.options = options; From c9e04a2aa4236f21338ee170dcef1a7264016968 Mon Sep 17 00:00:00 2001 From: HarrisonTCodes Date: Fri, 31 Oct 2025 23:41:43 +0000 Subject: [PATCH 09/12] fix: improved path normalization in mock file stream for relative paths --- .../MockFileStream.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs index 77553b851..79e58759f 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs @@ -55,7 +55,7 @@ public MockFileStream( ThrowIfInvalidModeAccess(mode, access); this.mockFileDataAccessor = mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor)); - path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar).TrimSlashes(); + path = NormalizePath(path); this.path = path; this.options = options; @@ -360,4 +360,15 @@ private TimeAdjustments GetTimeAdjustmentsForFileStreamWhenFileExists(FileMode m return TimeAdjustments.None; } } + + private string NormalizePath(string path) + { + var normalizedPath = path + .Replace( + mockFileDataAccessor.Path.AltDirectorySeparatorChar, + mockFileDataAccessor.Path.DirectorySeparatorChar) + .TrimSlashes(); + normalizedPath = mockFileDataAccessor.Path.GetFullPath(normalizedPath); + return normalizedPath; + } } \ No newline at end of file From f241cc258a2002273dade3512122a3eef7e1d132 Mon Sep 17 00:00:00 2001 From: HarrisonTCodes Date: Sun, 9 Nov 2025 10:20:10 +0000 Subject: [PATCH 10/12] fix: added improved handling of file stream options in factory method --- .../MockFileStreamFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStreamFactory.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStreamFactory.cs index 5cfb2a5a0..1dc39759c 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStreamFactory.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStreamFactory.cs @@ -59,7 +59,7 @@ public FileSystemStream New(string path, FileMode mode, FileAccess access, FileS #if FEATURE_FILESTREAM_OPTIONS /// public FileSystemStream New(string path, FileStreamOptions options) - => new MockFileStream(mockFileSystem, path, options.Mode, options.Access, options: options.Options); + => new MockFileStream(mockFileSystem, path, options.Mode, options.Access, options.Share, options.Options); #endif /// From eabc64e624c45a2e8fb8b57619859e7210ade1d8 Mon Sep 17 00:00:00 2001 From: HarrisonTCodes Date: Sun, 9 Nov 2025 12:25:46 +0000 Subject: [PATCH 11/12] refactor: de-duplicated normalize/fix path logic moving method to path verifier --- .../MockFileStream.cs | 13 +------ .../MockFileSystem.cs | 35 ++++++++----------- .../PathVerifier.cs | 19 ++++++++++ 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs index 79e58759f..05ab473f5 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs @@ -55,7 +55,7 @@ public MockFileStream( ThrowIfInvalidModeAccess(mode, access); this.mockFileDataAccessor = mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor)); - path = NormalizePath(path); + path = mockFileDataAccessor.PathVerifier.FixPath(path); this.path = path; this.options = options; @@ -360,15 +360,4 @@ private TimeAdjustments GetTimeAdjustmentsForFileStreamWhenFileExists(FileMode m return TimeAdjustments.None; } } - - private string NormalizePath(string path) - { - var normalizedPath = path - .Replace( - mockFileDataAccessor.Path.AltDirectorySeparatorChar, - mockFileDataAccessor.Path.DirectorySeparatorChar) - .TrimSlashes(); - normalizedPath = mockFileDataAccessor.Path.GetFullPath(normalizedPath); - return normalizedPath; - } } \ No newline at end of file diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs index 3f71206cb..34b809c21 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs @@ -128,19 +128,6 @@ public MockFileSystem MockTime(Func dateTimeProvider) return this; } - private string FixPath(string path, bool checkCaps = false) - { - if (path == null) - { - throw new ArgumentNullException(nameof(path), StringResources.Manager.GetString("VALUE_CANNOT_BE_NULL")); - } - - var pathSeparatorFixed = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - var fullPath = Path.GetFullPath(pathSeparatorFixed); - - return checkCaps ? GetPathWithCorrectDirectoryCapitalization(fullPath) : fullPath; - } - //If C:\foo exists, ensures that trying to save a file to "C:\FOO\file.txt" instead saves it to "C:\foo\file.txt". private string GetPathWithCorrectDirectoryCapitalization(string fullPath) { @@ -194,7 +181,7 @@ public MockFileData AdjustTimes(MockFileData fileData, TimeAdjustments timeAdjus /// public MockFileData GetFile(string path) { - path = FixPath(path).TrimSlashes(); + path = pathVerifier.FixPath(path).TrimSlashes(); return GetFileWithoutFixingPath(path); } @@ -210,7 +197,9 @@ public MockDriveData GetDrive(string name) private void SetEntry(string path, MockFileData mockFile) { - path = FixPath(path, true).TrimSlashes(); + path = GetPathWithCorrectDirectoryCapitalization( + pathVerifier.FixPath(path) + ).TrimSlashes(); lock (files) { @@ -232,7 +221,9 @@ private void SetEntry(string path, MockFileData mockFile) /// public void AddFile(string path, MockFileData mockFile, bool verifyAccess = true) { - var fixedPath = FixPath(path, true); + var fixedPath = GetPathWithCorrectDirectoryCapitalization( + pathVerifier.FixPath(path) + ); mockFile ??= new MockFileData(string.Empty); var file = GetFile(fixedPath); @@ -319,7 +310,9 @@ public MockFileData GetFile(IFileInfo path) /// public void AddDirectory(string path) { - var fixedPath = FixPath(path, true); + var fixedPath = GetPathWithCorrectDirectoryCapitalization( + pathVerifier.FixPath(path) + ); var separator = Path.DirectorySeparatorChar.ToString(); if (FileExists(fixedPath) && FileIsReadOnly(fixedPath)) @@ -408,8 +401,8 @@ public void AddDrive(string name, MockDriveData mockDrive) /// public void MoveDirectory(string sourcePath, string destPath) { - sourcePath = FixPath(sourcePath); - destPath = FixPath(destPath); + sourcePath = pathVerifier.FixPath(sourcePath); + destPath = pathVerifier.FixPath(destPath); var sourcePathSequence = sourcePath.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); @@ -452,7 +445,7 @@ bool PathStartsWith(string path, string[] minMatch) /// public void RemoveFile(string path, bool verifyAccess = true) { - path = FixPath(path); + path = pathVerifier.FixPath(path); lock (files) { @@ -473,7 +466,7 @@ public bool FileExists(string path) return false; } - path = FixPath(path).TrimSlashes(); + path = pathVerifier.FixPath(path).TrimSlashes(); lock (files) { diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/PathVerifier.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/PathVerifier.cs index 2c426637c..93405eebb 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/PathVerifier.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/PathVerifier.cs @@ -183,4 +183,23 @@ public bool TryNormalizeDriveName(string name, out string result) result = name; return true; } + + /// + /// Resolves and normalizes a path. + /// + public string FixPath(string path) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path), StringResources.Manager.GetString("VALUE_CANNOT_BE_NULL")); + } + + var pathSeparatorFixed = path.Replace( + _mockFileDataAccessor.Path.AltDirectorySeparatorChar, + _mockFileDataAccessor.Path.DirectorySeparatorChar + ); + var fullPath = _mockFileDataAccessor.Path.GetFullPath(pathSeparatorFixed); + + return fullPath; + } } From 2db45c1b46574755cf23dde6cb7b42dbb0d67b23 Mon Sep 17 00:00:00 2001 From: HarrisonTCodes Date: Sun, 9 Nov 2025 12:29:01 +0000 Subject: [PATCH 12/12] chore: explicit API acceptance test changes to cover path verifier change --- .../TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt | 1 + .../TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt | 1 + .../TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt | 1 + .../TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt | 1 + ...leIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt | 1 + ...leIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt | 1 + 6 files changed, 6 insertions(+) diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt index 386e51112..094f901ae 100644 --- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt +++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt @@ -468,6 +468,7 @@ namespace System.IO.Abstractions.TestingHelpers { public PathVerifier(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor) { } public void CheckInvalidPathChars(string path, bool checkAdditional = false) { } + public string FixPath(string path) { } public bool HasIllegalCharacters(string path, bool checkAdditional) { } public void IsLegalAbsoluteOrRelative(string path, string paramName) { } public string NormalizeDriveName(string name) { } diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt index 30e5f6341..d8dcd5d80 100644 --- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt +++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt @@ -524,6 +524,7 @@ namespace System.IO.Abstractions.TestingHelpers { public PathVerifier(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor) { } public void CheckInvalidPathChars(string path, bool checkAdditional = false) { } + public string FixPath(string path) { } public bool HasIllegalCharacters(string path, bool checkAdditional) { } public void IsLegalAbsoluteOrRelative(string path, string paramName) { } public string NormalizeDriveName(string name) { } diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt index a7216a7b7..d5d7baa8e 100644 --- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt +++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt @@ -549,6 +549,7 @@ namespace System.IO.Abstractions.TestingHelpers { public PathVerifier(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor) { } public void CheckInvalidPathChars(string path, bool checkAdditional = false) { } + public string FixPath(string path) { } public bool HasIllegalCharacters(string path, bool checkAdditional) { } public void IsLegalAbsoluteOrRelative(string path, string paramName) { } public string NormalizeDriveName(string name) { } diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt index 1e94ced04..b59eb5dbc 100644 --- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt +++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt @@ -563,6 +563,7 @@ namespace System.IO.Abstractions.TestingHelpers { public PathVerifier(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor) { } public void CheckInvalidPathChars(string path, bool checkAdditional = false) { } + public string FixPath(string path) { } public bool HasIllegalCharacters(string path, bool checkAdditional) { } public void IsLegalAbsoluteOrRelative(string path, string paramName) { } public string NormalizeDriveName(string name) { } diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt index e12d0f471..39357be50 100644 --- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt +++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt @@ -468,6 +468,7 @@ namespace System.IO.Abstractions.TestingHelpers { public PathVerifier(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor) { } public void CheckInvalidPathChars(string path, bool checkAdditional = false) { } + public string FixPath(string path) { } public bool HasIllegalCharacters(string path, bool checkAdditional) { } public void IsLegalAbsoluteOrRelative(string path, string paramName) { } public string NormalizeDriveName(string name) { } diff --git a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt index bead7ec0e..df8c9c634 100644 --- a/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt +++ b/tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.1.txt @@ -497,6 +497,7 @@ namespace System.IO.Abstractions.TestingHelpers { public PathVerifier(System.IO.Abstractions.TestingHelpers.IMockFileDataAccessor mockFileDataAccessor) { } public void CheckInvalidPathChars(string path, bool checkAdditional = false) { } + public string FixPath(string path) { } public bool HasIllegalCharacters(string path, bool checkAdditional) { } public void IsLegalAbsoluteOrRelative(string path, string paramName) { } public string NormalizeDriveName(string name) { }