diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/IMockFileDataAccessor.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/IMockFileDataAccessor.cs index ba479cdd8..abe98671c 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/IMockFileDataAccessor.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/IMockFileDataAccessor.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Reflection; namespace System.IO.Abstractions.TestingHelpers; @@ -110,4 +111,9 @@ public interface IMockFileDataAccessor : IFileSystem /// Gets a reference to the underlying file system. /// IFileSystem FileSystem { get; } + + /// + /// Gets a reference to the open file handles. + /// + ConcurrentDictionary> FileHandles { get; } } \ No newline at end of file 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/MockFileStream.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs index 1be2241af..0908dbaca 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.Concurrent; namespace System.IO.Abstractions.TestingHelpers; @@ -31,7 +32,9 @@ public NullFileSystemStream() : base(Null, ".", true) private readonly IMockFileDataAccessor mockFileDataAccessor; private readonly string path; + private readonly Guid guid = Guid.NewGuid(); private readonly FileAccess access = FileAccess.ReadWrite; + private readonly FileShare share = FileShare.Read; private readonly FileOptions options; private readonly MockFileData fileData; private bool disposed; @@ -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), @@ -51,6 +55,7 @@ public MockFileStream( ThrowIfInvalidModeAccess(mode, access); this.mockFileDataAccessor = mockFileDataAccessor ?? throw new ArgumentNullException(nameof(mockFileDataAccessor)); + path = mockFileDataAccessor.PathVerifier.FixPath(path); this.path = path; this.options = options; @@ -97,7 +102,39 @@ public MockFileStream( mockFileDataAccessor.AddFile(path, fileData); } + var fileHandlesEntry = mockFileDataAccessor.FileHandles.GetOrAdd( + path, + _ => new ConcurrentDictionary()); + + var requiredShare = AccessToShare(access); + foreach (var (existingAccess, existingShare) in fileHandlesEntry.Values) + { + var existingRequiredShare = AccessToShare(existingAccess); + var existingBlocksNew = (existingShare & requiredShare) != requiredShare; + var newBlocksExisting = (share & existingRequiredShare) != existingRequiredShare; + if (existingBlocksNew || newBlocksExisting) + { + throw CommonExceptions.ProcessCannotAccessFileInUse(path); + } + } + + fileHandlesEntry[guid] = (access, share); this.access = access; + this.share = share; + } + + private static FileShare AccessToShare(FileAccess access) + { + var share = FileShare.None; + if (access.HasFlag(FileAccess.Read)) + { + share |= FileShare.Read; + } + if (access.HasFlag(FileAccess.Write)) + { + share |= FileShare.Write; + } + return share; } private static void ThrowIfInvalidModeAccess(FileMode mode, FileAccess access) @@ -144,6 +181,14 @@ protected override void Dispose(bool disposing) { return; } + if (mockFileDataAccessor.FileHandles.TryGetValue(path, out var fileHandlesEntry)) + { + fileHandlesEntry.TryRemove(guid, out _); + if (fileHandlesEntry.IsEmpty) + { + mockFileDataAccessor.FileHandles.TryRemove(path, out _); + } + } InternalFlush(); base.Dispose(disposing); OnClose(); diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStreamFactory.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStreamFactory.cs index 9cd34f7ae..1dc39759c 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.Share, options.Options); #endif /// diff --git a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs index 3f71206cb..969b09996 100644 --- a/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs +++ b/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs @@ -1,3 +1,4 @@ +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -21,6 +22,10 @@ public class MockFileSystem : FileSystemBase, IMockFileDataAccessor private readonly PathVerifier pathVerifier; #if FEATURE_SERIALIZABLE [NonSerialized] +#endif + private readonly ConcurrentDictionary> fileHandles = new(); +#if FEATURE_SERIALIZABLE + [NonSerialized] #endif private Func dateTimeProvider = defaultDateTimeProvider; private static Func defaultDateTimeProvider = () => DateTime.UtcNow; @@ -114,6 +119,9 @@ public MockFileSystem(IDictionary files, MockFileSystemOpt public IFileSystem FileSystem => this; /// public PathVerifier PathVerifier => pathVerifier; + /// + public ConcurrentDictionary> FileHandles + => fileHandles; /// /// Replaces the time provider with a mocked instance. This allows to influence the used time in tests. @@ -128,19 +136,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 +189,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 +205,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 +229,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 +318,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 +409,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 +453,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 +474,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; + } } 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..0df64b93c 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 @@ -8,6 +8,7 @@ namespace System.IO.Abstractions.TestingHelpers System.Collections.Generic.IEnumerable AllDrives { get; } System.Collections.Generic.IEnumerable AllFiles { get; } System.Collections.Generic.IEnumerable AllPaths { get; } + System.Collections.Concurrent.ConcurrentDictionary>> FileHandles { get; } System.IO.Abstractions.IFileSystem FileSystem { get; } System.IO.Abstractions.TestingHelpers.PathVerifier PathVerifier { get; } System.IO.Abstractions.TestingHelpers.StringOperations StringOperations { get; } @@ -297,7 +298,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; } @@ -347,6 +348,7 @@ namespace System.IO.Abstractions.TestingHelpers public override System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; } public override System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; } public override System.IO.Abstractions.IFile File { get; } + public System.Collections.Concurrent.ConcurrentDictionary>> FileHandles { get; } public override System.IO.Abstractions.IFileInfoFactory FileInfo { get; } public override System.IO.Abstractions.IFileStreamFactory FileStream { get; } public System.IO.Abstractions.IFileSystem FileSystem { get; } @@ -468,6 +470,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 afd2ce3c4..7498e9a31 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 @@ -8,6 +8,7 @@ namespace System.IO.Abstractions.TestingHelpers System.Collections.Generic.IEnumerable AllDrives { get; } System.Collections.Generic.IEnumerable AllFiles { get; } System.Collections.Generic.IEnumerable AllPaths { get; } + System.Collections.Concurrent.ConcurrentDictionary>> FileHandles { get; } System.IO.Abstractions.IFileSystem FileSystem { get; } System.IO.Abstractions.TestingHelpers.PathVerifier PathVerifier { get; } System.IO.Abstractions.TestingHelpers.StringOperations StringOperations { get; } @@ -346,7 +347,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; } @@ -402,6 +403,7 @@ namespace System.IO.Abstractions.TestingHelpers public override System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; } public override System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; } public override System.IO.Abstractions.IFile File { get; } + public System.Collections.Concurrent.ConcurrentDictionary>> FileHandles { get; } public override System.IO.Abstractions.IFileInfoFactory FileInfo { get; } public override System.IO.Abstractions.IFileStreamFactory FileStream { get; } public System.IO.Abstractions.IFileSystem FileSystem { get; } @@ -524,6 +526,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 1c581cab8..1fe7676be 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 @@ -8,6 +8,7 @@ namespace System.IO.Abstractions.TestingHelpers System.Collections.Generic.IEnumerable AllDrives { get; } System.Collections.Generic.IEnumerable AllFiles { get; } System.Collections.Generic.IEnumerable AllPaths { get; } + System.Collections.Concurrent.ConcurrentDictionary>> FileHandles { get; } System.IO.Abstractions.IFileSystem FileSystem { get; } System.IO.Abstractions.TestingHelpers.PathVerifier PathVerifier { get; } System.IO.Abstractions.TestingHelpers.StringOperations StringOperations { get; } @@ -370,7 +371,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; } @@ -426,6 +427,7 @@ namespace System.IO.Abstractions.TestingHelpers public override System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; } public override System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; } public override System.IO.Abstractions.IFile File { get; } + public System.Collections.Concurrent.ConcurrentDictionary>> FileHandles { get; } public override System.IO.Abstractions.IFileInfoFactory FileInfo { get; } public override System.IO.Abstractions.IFileStreamFactory FileStream { get; } public System.IO.Abstractions.IFileSystem FileSystem { get; } @@ -549,6 +551,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 6c78cf677..f119d8b86 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 @@ -8,6 +8,7 @@ namespace System.IO.Abstractions.TestingHelpers System.Collections.Generic.IEnumerable AllDrives { get; } System.Collections.Generic.IEnumerable AllFiles { get; } System.Collections.Generic.IEnumerable AllPaths { get; } + System.Collections.Concurrent.ConcurrentDictionary>> FileHandles { get; } System.IO.Abstractions.IFileSystem FileSystem { get; } System.IO.Abstractions.TestingHelpers.PathVerifier PathVerifier { get; } System.IO.Abstractions.TestingHelpers.StringOperations StringOperations { get; } @@ -384,7 +385,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; } @@ -440,6 +441,7 @@ namespace System.IO.Abstractions.TestingHelpers public override System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; } public override System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; } public override System.IO.Abstractions.IFile File { get; } + public System.Collections.Concurrent.ConcurrentDictionary>> FileHandles { get; } public override System.IO.Abstractions.IFileInfoFactory FileInfo { get; } public override System.IO.Abstractions.IFileStreamFactory FileStream { get; } public System.IO.Abstractions.IFileSystem FileSystem { get; } @@ -563,6 +565,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 e0880b9f8..d8bcdae18 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 @@ -8,6 +8,7 @@ namespace System.IO.Abstractions.TestingHelpers System.Collections.Generic.IEnumerable AllDrives { get; } System.Collections.Generic.IEnumerable AllFiles { get; } System.Collections.Generic.IEnumerable AllPaths { get; } + System.Collections.Concurrent.ConcurrentDictionary>> FileHandles { get; } System.IO.Abstractions.IFileSystem FileSystem { get; } System.IO.Abstractions.TestingHelpers.PathVerifier PathVerifier { get; } System.IO.Abstractions.TestingHelpers.StringOperations StringOperations { get; } @@ -297,7 +298,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; } @@ -347,6 +348,7 @@ namespace System.IO.Abstractions.TestingHelpers public override System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; } public override System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; } public override System.IO.Abstractions.IFile File { get; } + public System.Collections.Concurrent.ConcurrentDictionary>> FileHandles { get; } public override System.IO.Abstractions.IFileInfoFactory FileInfo { get; } public override System.IO.Abstractions.IFileStreamFactory FileStream { get; } public System.IO.Abstractions.IFileSystem FileSystem { get; } @@ -468,6 +470,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 80f876c0c..edf06b564 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 @@ -8,6 +8,7 @@ namespace System.IO.Abstractions.TestingHelpers System.Collections.Generic.IEnumerable AllDrives { get; } System.Collections.Generic.IEnumerable AllFiles { get; } System.Collections.Generic.IEnumerable AllPaths { get; } + System.Collections.Concurrent.ConcurrentDictionary>> FileHandles { get; } System.IO.Abstractions.IFileSystem FileSystem { get; } System.IO.Abstractions.TestingHelpers.PathVerifier PathVerifier { get; } System.IO.Abstractions.TestingHelpers.StringOperations StringOperations { get; } @@ -323,7 +324,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; } @@ -375,6 +376,7 @@ namespace System.IO.Abstractions.TestingHelpers public override System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; } public override System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; } public override System.IO.Abstractions.IFile File { get; } + public System.Collections.Concurrent.ConcurrentDictionary>> FileHandles { get; } public override System.IO.Abstractions.IFileInfoFactory FileInfo { get; } public override System.IO.Abstractions.IFileStreamFactory FileStream { get; } public System.IO.Abstractions.IFileSystem FileSystem { get; } @@ -497,6 +499,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.TestingHelpers.Tests/MockFileStreamTests.cs b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs index b82566d9a..a8f815a15 100644 --- a/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs +++ b/tests/TestableIO.System.IO.Abstractions.TestingHelpers.Tests/MockFileStreamTests.cs @@ -390,4 +390,43 @@ async Task Act() => await source.CopyToAsync(destination); await That(Act).Throws(); } + + [Test] + [TestCase(FileShare.None, FileAccess.Read)] + [TestCase(FileShare.None, FileAccess.ReadWrite)] + [TestCase(FileShare.None, FileAccess.Write)] + [TestCase(FileShare.Read, FileAccess.Write)] + [TestCase(FileShare.Read, FileAccess.ReadWrite)] + [TestCase(FileShare.Write, FileAccess.Read)] + public async Task MockFileStream_ConflictingShareOrAccess_ShouldThrowUntilHandleReleased( + FileShare share, + FileAccess access) + { + var fileSystem = new MockFileSystem(); + fileSystem.File.WriteAllText("foo.txt", ""); + using (new MockFileStream(fileSystem, "foo.txt", FileMode.Open, FileAccess.Read, share)) + { + await That(() => new MockFileStream(fileSystem, "foo.txt", FileMode.Open, access)).Throws(); + } + await That(() => new MockFileStream(fileSystem, "foo.txt", FileMode.Open, access)).DoesNotThrow(); + } + + [Test] + [TestCase(FileShare.Read, FileAccess.Read)] + [TestCase(FileShare.Read | FileShare.Write, FileAccess.Read)] + [TestCase(FileShare.Read | FileShare.Write, FileAccess.ReadWrite)] + [TestCase(FileShare.ReadWrite, FileAccess.Read)] + [TestCase(FileShare.ReadWrite, FileAccess.ReadWrite)] + [TestCase(FileShare.ReadWrite, FileAccess.Write)] + public async Task MockFileStream_CompatibleShareOrAccess_ShouldNotThrow( + FileShare share, + FileAccess access) + { + var fileSystem = new MockFileSystem(); + fileSystem.File.WriteAllText("foo.txt", ""); + using (new MockFileStream(fileSystem, "foo.txt", FileMode.Open, FileAccess.Read, share)) + { + await That(() => new MockFileStream(fileSystem, "foo.txt", FileMode.Open, access)).DoesNotThrow(); + } + } } \ No newline at end of file