Skip to content

Commit a61e568

Browse files
feat: added file handles class and updated mock file system/stream to use it
1 parent 8b23795 commit a61e568

11 files changed

+118
-61
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.IO;
4+
using System.IO.Abstractions.TestingHelpers;
5+
6+
public class FileHandles
7+
{
8+
private readonly ConcurrentDictionary<string, ConcurrentDictionary<Guid, (FileAccess access, FileShare share)>> handles = new();
9+
10+
public void TryAddHandle(string path, Guid guid, FileAccess access, FileShare share)
11+
{
12+
var pathHandles = handles.GetOrAdd(
13+
path,
14+
_ => new ConcurrentDictionary<Guid, (FileAccess, FileShare)>());
15+
16+
var requiredShare = AccessToShare(access);
17+
foreach (var (existingAccess, existingShare) in pathHandles.Values)
18+
{
19+
var existingRequiredShare = AccessToShare(existingAccess);
20+
var existingBlocksNew = (existingShare & requiredShare) != requiredShare;
21+
var newBlocksExisting = (share & existingRequiredShare) != existingRequiredShare;
22+
if (existingBlocksNew || newBlocksExisting)
23+
{
24+
throw CommonExceptions.ProcessCannotAccessFileInUse(path);
25+
}
26+
}
27+
28+
pathHandles[guid] = (access, share);
29+
}
30+
31+
public void RemoveHandle(string path, Guid guid)
32+
{
33+
if (handles.TryGetValue(path, out var pathHandles))
34+
{
35+
pathHandles.TryRemove(guid, out _);
36+
if (pathHandles.IsEmpty)
37+
{
38+
handles.TryRemove(path, out _);
39+
}
40+
}
41+
}
42+
43+
private static FileShare AccessToShare(FileAccess access)
44+
{
45+
var share = FileShare.None;
46+
if (access.HasFlag(FileAccess.Read))
47+
{
48+
share |= FileShare.Read;
49+
}
50+
if (access.HasFlag(FileAccess.Write))
51+
{
52+
share |= FileShare.Write;
53+
}
54+
return share;
55+
}
56+
}

src/TestableIO.System.IO.Abstractions.TestingHelpers/IMockFileDataAccessor.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System.Collections.Concurrent;
2-
using System.Collections.Generic;
1+
using System.Collections.Generic;
32
using System.Reflection;
43

54
namespace System.IO.Abstractions.TestingHelpers;
@@ -115,5 +114,5 @@ public interface IMockFileDataAccessor : IFileSystem
115114
/// <summary>
116115
/// Gets a reference to the open file handles.
117116
/// </summary>
118-
ConcurrentDictionary<string, ConcurrentDictionary<Guid, (FileAccess, FileShare)>> FileHandles { get; }
117+
FileHandles FileHandles { get; }
119118
}

src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileStream.cs

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.Threading;
33
using System.Runtime.Versioning;
44
using System.Security.AccessControl;
5-
using System.Collections.Concurrent;
65

76
namespace System.IO.Abstractions.TestingHelpers;
87

@@ -102,41 +101,11 @@ public MockFileStream(
102101
mockFileDataAccessor.AddFile(path, fileData);
103102
}
104103

105-
var fileHandlesEntry = mockFileDataAccessor.FileHandles.GetOrAdd(
106-
path,
107-
_ => new ConcurrentDictionary<Guid, (FileAccess access, FileShare share)>());
108-
109-
var requiredShare = AccessToShare(access);
110-
foreach (var (existingAccess, existingShare) in fileHandlesEntry.Values)
111-
{
112-
var existingRequiredShare = AccessToShare(existingAccess);
113-
var existingBlocksNew = (existingShare & requiredShare) != requiredShare;
114-
var newBlocksExisting = (share & existingRequiredShare) != existingRequiredShare;
115-
if (existingBlocksNew || newBlocksExisting)
116-
{
117-
throw CommonExceptions.ProcessCannotAccessFileInUse(path);
118-
}
119-
}
120-
121-
fileHandlesEntry[guid] = (access, share);
104+
mockFileDataAccessor.FileHandles.TryAddHandle(path, guid, access, share);
122105
this.access = access;
123106
this.share = share;
124107
}
125108

126-
private static FileShare AccessToShare(FileAccess access)
127-
{
128-
var share = FileShare.None;
129-
if (access.HasFlag(FileAccess.Read))
130-
{
131-
share |= FileShare.Read;
132-
}
133-
if (access.HasFlag(FileAccess.Write))
134-
{
135-
share |= FileShare.Write;
136-
}
137-
return share;
138-
}
139-
140109
private static void ThrowIfInvalidModeAccess(FileMode mode, FileAccess access)
141110
{
142111
if (mode == FileMode.Append)
@@ -181,14 +150,7 @@ protected override void Dispose(bool disposing)
181150
{
182151
return;
183152
}
184-
if (mockFileDataAccessor.FileHandles.TryGetValue(path, out var fileHandlesEntry))
185-
{
186-
fileHandlesEntry.TryRemove(guid, out _);
187-
if (fileHandlesEntry.IsEmpty)
188-
{
189-
mockFileDataAccessor.FileHandles.TryRemove(path, out _);
190-
}
191-
}
153+
mockFileDataAccessor.FileHandles.RemoveHandle(path, guid);
192154
InternalFlush();
193155
base.Dispose(disposing);
194156
OnClose();

src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Collections.Concurrent;
21
using System.Collections.Generic;
32
using System.Linq;
43
using System.Reflection;
@@ -23,7 +22,7 @@ public class MockFileSystem : FileSystemBase, IMockFileDataAccessor
2322
#if FEATURE_SERIALIZABLE
2423
[NonSerialized]
2524
#endif
26-
private readonly ConcurrentDictionary<string, ConcurrentDictionary<Guid, (FileAccess access, FileShare share)>> fileHandles = new();
25+
private readonly FileHandles fileHandles = new();
2726
#if FEATURE_SERIALIZABLE
2827
[NonSerialized]
2928
#endif
@@ -120,8 +119,7 @@ public MockFileSystem(IDictionary<string, MockFileData> files, MockFileSystemOpt
120119
/// <inheritdoc />
121120
public PathVerifier PathVerifier => pathVerifier;
122121
/// <inheritdoc />
123-
public ConcurrentDictionary<string, ConcurrentDictionary<Guid, (FileAccess, FileShare)>> FileHandles
124-
=> fileHandles;
122+
public FileHandles FileHandles => fileHandles;
125123

126124
/// <summary>
127125
/// Replaces the time provider with a mocked instance. This allows to influence the used time in tests.

tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net10.0.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/TestableIO/System.IO.Abstractions.git")]
22
[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v10.0", FrameworkDisplayName=".NET 10.0")]
3+
public class FileHandles
4+
{
5+
public FileHandles() { }
6+
public void RemoveHandle(string path, System.Guid guid) { }
7+
public void TryAddHandle(string path, System.Guid guid, System.IO.FileAccess access, System.IO.FileShare share) { }
8+
}
39
namespace System.IO.Abstractions.TestingHelpers
410
{
511
public interface IMockFileDataAccessor : System.IO.Abstractions.IFileSystem
@@ -8,7 +14,7 @@ namespace System.IO.Abstractions.TestingHelpers
814
System.Collections.Generic.IEnumerable<string> AllDrives { get; }
915
System.Collections.Generic.IEnumerable<string> AllFiles { get; }
1016
System.Collections.Generic.IEnumerable<string> AllPaths { get; }
11-
System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentDictionary<System.Guid, System.ValueTuple<System.IO.FileAccess, System.IO.FileShare>>> FileHandles { get; }
17+
FileHandles FileHandles { get; }
1218
System.IO.Abstractions.IFileSystem FileSystem { get; }
1319
System.IO.Abstractions.TestingHelpers.PathVerifier PathVerifier { get; }
1420
System.IO.Abstractions.TestingHelpers.StringOperations StringOperations { get; }
@@ -441,7 +447,7 @@ namespace System.IO.Abstractions.TestingHelpers
441447
public override System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; }
442448
public override System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; }
443449
public override System.IO.Abstractions.IFile File { get; }
444-
public System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentDictionary<System.Guid, System.ValueTuple<System.IO.FileAccess, System.IO.FileShare>>> FileHandles { get; }
450+
public FileHandles FileHandles { get; }
445451
public override System.IO.Abstractions.IFileInfoFactory FileInfo { get; }
446452
public override System.IO.Abstractions.IFileStreamFactory FileStream { get; }
447453
public System.IO.Abstractions.IFileSystem FileSystem { get; }

tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net472.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/TestableIO/System.IO.Abstractions.git")]
22
[assembly: System.Runtime.Versioning.TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName=".NET Framework 4.7.2")]
3+
public class FileHandles
4+
{
5+
public FileHandles() { }
6+
public void RemoveHandle(string path, System.Guid guid) { }
7+
public void TryAddHandle(string path, System.Guid guid, System.IO.FileAccess access, System.IO.FileShare share) { }
8+
}
39
namespace System.IO.Abstractions.TestingHelpers
410
{
511
public interface IMockFileDataAccessor : System.IO.Abstractions.IFileSystem
@@ -8,7 +14,7 @@ namespace System.IO.Abstractions.TestingHelpers
814
System.Collections.Generic.IEnumerable<string> AllDrives { get; }
915
System.Collections.Generic.IEnumerable<string> AllFiles { get; }
1016
System.Collections.Generic.IEnumerable<string> AllPaths { get; }
11-
System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentDictionary<System.Guid, System.ValueTuple<System.IO.FileAccess, System.IO.FileShare>>> FileHandles { get; }
17+
FileHandles FileHandles { get; }
1218
System.IO.Abstractions.IFileSystem FileSystem { get; }
1319
System.IO.Abstractions.TestingHelpers.PathVerifier PathVerifier { get; }
1420
System.IO.Abstractions.TestingHelpers.StringOperations StringOperations { get; }
@@ -348,7 +354,7 @@ namespace System.IO.Abstractions.TestingHelpers
348354
public override System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; }
349355
public override System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; }
350356
public override System.IO.Abstractions.IFile File { get; }
351-
public System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentDictionary<System.Guid, System.ValueTuple<System.IO.FileAccess, System.IO.FileShare>>> FileHandles { get; }
357+
public FileHandles FileHandles { get; }
352358
public override System.IO.Abstractions.IFileInfoFactory FileInfo { get; }
353359
public override System.IO.Abstractions.IFileStreamFactory FileStream { get; }
354360
public System.IO.Abstractions.IFileSystem FileSystem { get; }

tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net6.0.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/TestableIO/System.IO.Abstractions.git")]
22
[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName=".NET 6.0")]
3+
public class FileHandles
4+
{
5+
public FileHandles() { }
6+
public void RemoveHandle(string path, System.Guid guid) { }
7+
public void TryAddHandle(string path, System.Guid guid, System.IO.FileAccess access, System.IO.FileShare share) { }
8+
}
39
namespace System.IO.Abstractions.TestingHelpers
410
{
511
public interface IMockFileDataAccessor : System.IO.Abstractions.IFileSystem
@@ -8,7 +14,7 @@ namespace System.IO.Abstractions.TestingHelpers
814
System.Collections.Generic.IEnumerable<string> AllDrives { get; }
915
System.Collections.Generic.IEnumerable<string> AllFiles { get; }
1016
System.Collections.Generic.IEnumerable<string> AllPaths { get; }
11-
System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentDictionary<System.Guid, System.ValueTuple<System.IO.FileAccess, System.IO.FileShare>>> FileHandles { get; }
17+
FileHandles FileHandles { get; }
1218
System.IO.Abstractions.IFileSystem FileSystem { get; }
1319
System.IO.Abstractions.TestingHelpers.PathVerifier PathVerifier { get; }
1420
System.IO.Abstractions.TestingHelpers.StringOperations StringOperations { get; }
@@ -403,7 +409,7 @@ namespace System.IO.Abstractions.TestingHelpers
403409
public override System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; }
404410
public override System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; }
405411
public override System.IO.Abstractions.IFile File { get; }
406-
public System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentDictionary<System.Guid, System.ValueTuple<System.IO.FileAccess, System.IO.FileShare>>> FileHandles { get; }
412+
public FileHandles FileHandles { get; }
407413
public override System.IO.Abstractions.IFileInfoFactory FileInfo { get; }
408414
public override System.IO.Abstractions.IFileStreamFactory FileStream { get; }
409415
public System.IO.Abstractions.IFileSystem FileSystem { get; }

tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net8.0.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/TestableIO/System.IO.Abstractions.git")]
22
[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName=".NET 8.0")]
3+
public class FileHandles
4+
{
5+
public FileHandles() { }
6+
public void RemoveHandle(string path, System.Guid guid) { }
7+
public void TryAddHandle(string path, System.Guid guid, System.IO.FileAccess access, System.IO.FileShare share) { }
8+
}
39
namespace System.IO.Abstractions.TestingHelpers
410
{
511
public interface IMockFileDataAccessor : System.IO.Abstractions.IFileSystem
@@ -8,7 +14,7 @@ namespace System.IO.Abstractions.TestingHelpers
814
System.Collections.Generic.IEnumerable<string> AllDrives { get; }
915
System.Collections.Generic.IEnumerable<string> AllFiles { get; }
1016
System.Collections.Generic.IEnumerable<string> AllPaths { get; }
11-
System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentDictionary<System.Guid, System.ValueTuple<System.IO.FileAccess, System.IO.FileShare>>> FileHandles { get; }
17+
FileHandles FileHandles { get; }
1218
System.IO.Abstractions.IFileSystem FileSystem { get; }
1319
System.IO.Abstractions.TestingHelpers.PathVerifier PathVerifier { get; }
1420
System.IO.Abstractions.TestingHelpers.StringOperations StringOperations { get; }
@@ -427,7 +433,7 @@ namespace System.IO.Abstractions.TestingHelpers
427433
public override System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; }
428434
public override System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; }
429435
public override System.IO.Abstractions.IFile File { get; }
430-
public System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentDictionary<System.Guid, System.ValueTuple<System.IO.FileAccess, System.IO.FileShare>>> FileHandles { get; }
436+
public FileHandles FileHandles { get; }
431437
public override System.IO.Abstractions.IFileInfoFactory FileInfo { get; }
432438
public override System.IO.Abstractions.IFileStreamFactory FileStream { get; }
433439
public System.IO.Abstractions.IFileSystem FileSystem { get; }

tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_net9.0.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/TestableIO/System.IO.Abstractions.git")]
22
[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v9.0", FrameworkDisplayName=".NET 9.0")]
3+
public class FileHandles
4+
{
5+
public FileHandles() { }
6+
public void RemoveHandle(string path, System.Guid guid) { }
7+
public void TryAddHandle(string path, System.Guid guid, System.IO.FileAccess access, System.IO.FileShare share) { }
8+
}
39
namespace System.IO.Abstractions.TestingHelpers
410
{
511
public interface IMockFileDataAccessor : System.IO.Abstractions.IFileSystem
@@ -8,7 +14,7 @@ namespace System.IO.Abstractions.TestingHelpers
814
System.Collections.Generic.IEnumerable<string> AllDrives { get; }
915
System.Collections.Generic.IEnumerable<string> AllFiles { get; }
1016
System.Collections.Generic.IEnumerable<string> AllPaths { get; }
11-
System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentDictionary<System.Guid, System.ValueTuple<System.IO.FileAccess, System.IO.FileShare>>> FileHandles { get; }
17+
FileHandles FileHandles { get; }
1218
System.IO.Abstractions.IFileSystem FileSystem { get; }
1319
System.IO.Abstractions.TestingHelpers.PathVerifier PathVerifier { get; }
1420
System.IO.Abstractions.TestingHelpers.StringOperations StringOperations { get; }
@@ -441,7 +447,7 @@ namespace System.IO.Abstractions.TestingHelpers
441447
public override System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; }
442448
public override System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; }
443449
public override System.IO.Abstractions.IFile File { get; }
444-
public System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentDictionary<System.Guid, System.ValueTuple<System.IO.FileAccess, System.IO.FileShare>>> FileHandles { get; }
450+
public FileHandles FileHandles { get; }
445451
public override System.IO.Abstractions.IFileInfoFactory FileInfo { get; }
446452
public override System.IO.Abstractions.IFileStreamFactory FileStream { get; }
447453
public System.IO.Abstractions.IFileSystem FileSystem { get; }

tests/TestableIO.System.IO.Abstractions.Api.Tests/Expected/TestableIO.System.IO.Abstractions.TestingHelpers_netstandard2.0.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/TestableIO/System.IO.Abstractions.git")]
22
[assembly: System.Runtime.Versioning.TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName=".NET Standard 2.0")]
3+
public class FileHandles
4+
{
5+
public FileHandles() { }
6+
public void RemoveHandle(string path, System.Guid guid) { }
7+
public void TryAddHandle(string path, System.Guid guid, System.IO.FileAccess access, System.IO.FileShare share) { }
8+
}
39
namespace System.IO.Abstractions.TestingHelpers
410
{
511
public interface IMockFileDataAccessor : System.IO.Abstractions.IFileSystem
@@ -8,7 +14,7 @@ namespace System.IO.Abstractions.TestingHelpers
814
System.Collections.Generic.IEnumerable<string> AllDrives { get; }
915
System.Collections.Generic.IEnumerable<string> AllFiles { get; }
1016
System.Collections.Generic.IEnumerable<string> AllPaths { get; }
11-
System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentDictionary<System.Guid, System.ValueTuple<System.IO.FileAccess, System.IO.FileShare>>> FileHandles { get; }
17+
FileHandles FileHandles { get; }
1218
System.IO.Abstractions.IFileSystem FileSystem { get; }
1319
System.IO.Abstractions.TestingHelpers.PathVerifier PathVerifier { get; }
1420
System.IO.Abstractions.TestingHelpers.StringOperations StringOperations { get; }
@@ -348,7 +354,7 @@ namespace System.IO.Abstractions.TestingHelpers
348354
public override System.IO.Abstractions.IDirectoryInfoFactory DirectoryInfo { get; }
349355
public override System.IO.Abstractions.IDriveInfoFactory DriveInfo { get; }
350356
public override System.IO.Abstractions.IFile File { get; }
351-
public System.Collections.Concurrent.ConcurrentDictionary<string, System.Collections.Concurrent.ConcurrentDictionary<System.Guid, System.ValueTuple<System.IO.FileAccess, System.IO.FileShare>>> FileHandles { get; }
357+
public FileHandles FileHandles { get; }
352358
public override System.IO.Abstractions.IFileInfoFactory FileInfo { get; }
353359
public override System.IO.Abstractions.IFileStreamFactory FileStream { get; }
354360
public System.IO.Abstractions.IFileSystem FileSystem { get; }

0 commit comments

Comments
 (0)