Skip to content

Commit 9912392

Browse files
authored
feat: Add TotalCount to Statistics (#604)
* Add TotalCount to Statistics * Move FileSystemRegistration to "Helpers" namespace
1 parent 31315ac commit 9912392

28 files changed

+537
-502
lines changed

Source/Testably.Abstractions.Testing/Helpers/FileSystemExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ internal static IDisposable IgnoreStatistics(this IFileSystem fileSystem)
129129
{
130130
if (fileSystem is MockFileSystem mockFileSystem)
131131
{
132-
return mockFileSystem.StatisticsRegistration.Ignore();
132+
return mockFileSystem.Registration.Ignore();
133133
}
134134

135135
return new NoOpDisposable();
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System;
2+
using System.Threading;
3+
using Testably.Abstractions.Testing.Statistics;
4+
5+
namespace Testably.Abstractions.Testing.Helpers;
6+
7+
internal sealed class FileSystemRegistration : IStatisticsGate
8+
{
9+
private static readonly AsyncLocal<bool> IsDisabled = new();
10+
private static readonly AsyncLocal<bool> IsInit = new();
11+
12+
/// <summary>
13+
/// The total count of registered statistic calls.
14+
/// </summary>
15+
public int TotalCount => _counter;
16+
17+
private int _counter;
18+
19+
#region IStatisticsGate Members
20+
21+
/// <inheritdoc cref="IStatisticsGate.GetCounter()" />
22+
public int GetCounter()
23+
{
24+
return Interlocked.Increment(ref _counter);
25+
}
26+
27+
/// <inheritdoc cref="IStatisticsGate.TryGetLock(out IDisposable)" />
28+
public bool TryGetLock(out IDisposable release)
29+
{
30+
if (IsDisabled.Value)
31+
{
32+
release = TemporaryDisable.None;
33+
return false;
34+
}
35+
36+
IsDisabled.Value = true;
37+
release = new TemporaryDisable(() => IsDisabled.Value = false);
38+
return true;
39+
}
40+
41+
#endregion
42+
43+
/// <summary>
44+
/// Ignores all registrations until the return value is disposed.
45+
/// </summary>
46+
internal IDisposable Ignore()
47+
{
48+
if (IsDisabled.Value)
49+
{
50+
return TemporaryDisable.None;
51+
}
52+
53+
IsDisabled.Value = true;
54+
IsInit.Value = true;
55+
return new TemporaryDisable(() =>
56+
{
57+
IsDisabled.Value = false;
58+
IsInit.Value = false;
59+
});
60+
}
61+
62+
internal bool IsInitializing()
63+
=> IsInit.Value;
64+
65+
private sealed class TemporaryDisable : IDisposable
66+
{
67+
public static IDisposable None { get; } = new NoOpDisposable();
68+
69+
private readonly Action _onDispose;
70+
71+
public TemporaryDisable(Action onDispose)
72+
{
73+
_onDispose = onDispose;
74+
}
75+
76+
#region IDisposable Members
77+
78+
/// <inheritdoc cref="IDisposable.Dispose()" />
79+
public void Dispose() => _onDispose();
80+
81+
#endregion
82+
}
83+
}

Source/Testably.Abstractions.Testing/MockFileSystem.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ internal ISafeFileHandleStrategy SafeFileHandleStrategy
8080
private set;
8181
}
8282

83+
internal FileSystemStatistics StatisticsRegistration { get; }
84+
8385
/// <summary>
8486
/// The underlying storage of directories and files.
8587
/// </summary>
@@ -91,7 +93,7 @@ internal ISafeFileHandleStrategy SafeFileHandleStrategy
9193
internal IReadOnlyList<IStorageContainer> StorageContainers
9294
=> _storage.GetContainers();
9395

94-
internal readonly FileSystemStatistics StatisticsRegistration;
96+
internal FileSystemRegistration Registration { get; }
9597

9698
private readonly DirectoryMock _directoryMock;
9799
private readonly FileMock _fileMock;
@@ -120,8 +122,9 @@ public MockFileSystem(Func<MockFileSystemOptions, MockFileSystemOptions> options
120122
SimulationMode = SimulationMode.Native;
121123
Execute = new Execute(this);
122124
#endif
125+
Registration = new FileSystemRegistration();
123126
StatisticsRegistration = new FileSystemStatistics(this);
124-
using IDisposable release = StatisticsRegistration.Ignore();
127+
using IDisposable release = Registration.Ignore();
125128
RandomSystem =
126129
new MockRandomSystem(initialization.RandomProvider ?? RandomProvider.Default());
127130
TimeSystem = new MockTimeSystem(TimeProvider.Now());
Lines changed: 34 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,7 @@
1-
using System;
2-
using System.Threading;
3-
using Testably.Abstractions.Testing.Helpers;
1+
namespace Testably.Abstractions.Testing.Statistics;
42

5-
namespace Testably.Abstractions.Testing.Statistics;
6-
7-
internal sealed class FileSystemStatistics : IFileSystemStatistics, IStatisticsGate
3+
internal sealed class FileSystemStatistics : IFileSystemStatistics
84
{
9-
private static readonly AsyncLocal<bool> IsDisabled = new();
10-
private static readonly AsyncLocal<bool> IsInit = new();
11-
12-
/// <summary>
13-
/// The total count of registered statistic calls.
14-
/// </summary>
15-
public int TotalCount => _counter;
16-
175
internal readonly CallStatistics<IDirectory> Directory;
186
internal readonly PathStatistics<IDirectoryInfoFactory, IDirectoryInfo> DirectoryInfo;
197
internal readonly PathStatistics<IDriveInfoFactory, IDriveInfo> DriveInfo;
@@ -25,118 +13,69 @@ internal readonly PathStatistics<IFileSystemWatcherFactory, IFileSystemWatcher>
2513
FileSystemWatcher;
2614

2715
internal readonly CallStatistics<IPath> Path;
28-
private int _counter;
16+
private readonly MockFileSystem _fileSystem;
2917

3018
public FileSystemStatistics(MockFileSystem fileSystem)
3119
{
32-
Directory = new CallStatistics<IDirectory>(this, nameof(IFileSystem.Directory));
20+
_fileSystem = fileSystem;
21+
IStatisticsGate statisticsGate = fileSystem.Registration;
22+
23+
Directory = new CallStatistics<IDirectory>(
24+
statisticsGate, nameof(IFileSystem.Directory));
3325
DirectoryInfo = new PathStatistics<IDirectoryInfoFactory, IDirectoryInfo>(
34-
this, fileSystem, nameof(IFileSystem.DirectoryInfo));
26+
statisticsGate, fileSystem, nameof(IFileSystem.DirectoryInfo));
3527
DriveInfo = new PathStatistics<IDriveInfoFactory, IDriveInfo>(
36-
this, fileSystem, nameof(IFileSystem.DriveInfo));
37-
File = new CallStatistics<IFile>(this, nameof(IFileSystem.File));
28+
statisticsGate, fileSystem, nameof(IFileSystem.DriveInfo));
29+
File = new CallStatistics<IFile>(
30+
statisticsGate, nameof(IFileSystem.File));
3831
FileInfo = new PathStatistics<IFileInfoFactory, IFileInfo>(
39-
this, fileSystem, nameof(IFileSystem.FileInfo));
32+
statisticsGate, fileSystem, nameof(IFileSystem.FileInfo));
4033
FileStream = new PathStatistics<IFileStreamFactory, FileSystemStream>(
41-
this, fileSystem, nameof(IFileSystem.FileStream));
34+
statisticsGate, fileSystem, nameof(IFileSystem.FileStream));
4235
FileSystemWatcher = new PathStatistics<IFileSystemWatcherFactory, IFileSystemWatcher>(
43-
this, fileSystem, nameof(IFileSystem.FileSystemWatcher));
44-
Path = new CallStatistics<IPath>(this, nameof(IFileSystem.Path));
36+
statisticsGate, fileSystem, nameof(IFileSystem.FileSystemWatcher));
37+
Path = new CallStatistics<IPath>(
38+
statisticsGate, nameof(IFileSystem.Path));
4539
}
4640

4741
#region IFileSystemStatistics Members
4842

43+
/// <inheritdoc cref="IFileSystemStatistics.TotalCount" />
44+
public int TotalCount
45+
=> _fileSystem.Registration.TotalCount;
46+
4947
/// <inheritdoc cref="IFileSystemStatistics.Directory" />
50-
IStatistics<IDirectory> IFileSystemStatistics.Directory => Directory;
48+
IStatistics<IDirectory> IFileSystemStatistics.Directory
49+
=> Directory;
5150

5251
/// <inheritdoc cref="IFileSystemStatistics.DirectoryInfo" />
5352
IPathStatistics<IDirectoryInfoFactory, IDirectoryInfo> IFileSystemStatistics.DirectoryInfo
5453
=> DirectoryInfo;
5554

5655
/// <inheritdoc cref="IFileSystemStatistics.DriveInfo" />
57-
IPathStatistics<IDriveInfoFactory, IDriveInfo> IFileSystemStatistics.DriveInfo => DriveInfo;
56+
IPathStatistics<IDriveInfoFactory, IDriveInfo> IFileSystemStatistics.DriveInfo
57+
=> DriveInfo;
5858

5959
/// <inheritdoc cref="IFileSystemStatistics.File" />
60-
IStatistics<IFile> IFileSystemStatistics.File => File;
60+
IStatistics<IFile> IFileSystemStatistics.File
61+
=> File;
6162

6263
/// <inheritdoc cref="IFileSystemStatistics.FileInfo" />
63-
IPathStatistics<IFileInfoFactory, IFileInfo> IFileSystemStatistics.FileInfo => FileInfo;
64+
IPathStatistics<IFileInfoFactory, IFileInfo> IFileSystemStatistics.FileInfo
65+
=> FileInfo;
6466

6567
/// <inheritdoc cref="IFileSystemStatistics.FileStream" />
6668
IPathStatistics<IFileStreamFactory, FileSystemStream> IFileSystemStatistics.FileStream
6769
=> FileStream;
6870

6971
/// <inheritdoc cref="IFileSystemStatistics.FileSystemWatcher" />
70-
IPathStatistics<IFileSystemWatcherFactory, IFileSystemWatcher> IFileSystemStatistics.
71-
FileSystemWatcher => FileSystemWatcher;
72+
IPathStatistics<IFileSystemWatcherFactory, IFileSystemWatcher>
73+
IFileSystemStatistics.FileSystemWatcher
74+
=> FileSystemWatcher;
7275

7376
/// <inheritdoc cref="IFileSystemStatistics.Path" />
74-
IStatistics<IPath> IFileSystemStatistics.Path => Path;
75-
76-
#endregion
77-
78-
#region IStatisticsGate Members
79-
80-
/// <inheritdoc cref="IStatisticsGate.GetCounter()" />
81-
public int GetCounter()
82-
{
83-
return Interlocked.Increment(ref _counter);
84-
}
85-
86-
/// <inheritdoc cref="IStatisticsGate.TryGetLock(out IDisposable)" />
87-
public bool TryGetLock(out IDisposable release)
88-
{
89-
if (IsDisabled.Value)
90-
{
91-
release = TemporaryDisable.None;
92-
return false;
93-
}
94-
95-
IsDisabled.Value = true;
96-
release = new TemporaryDisable(() => IsDisabled.Value = false);
97-
return true;
98-
}
77+
IStatistics<IPath> IFileSystemStatistics.Path
78+
=> Path;
9979

10080
#endregion
101-
102-
/// <summary>
103-
/// Ignores all registrations until the return value is disposed.
104-
/// </summary>
105-
internal IDisposable Ignore()
106-
{
107-
if (IsDisabled.Value)
108-
{
109-
return TemporaryDisable.None;
110-
}
111-
112-
IsDisabled.Value = true;
113-
IsInit.Value = true;
114-
return new TemporaryDisable(() =>
115-
{
116-
IsDisabled.Value = false;
117-
IsInit.Value = false;
118-
});
119-
}
120-
121-
internal bool IsInitializing()
122-
=> IsInit.Value;
123-
124-
private sealed class TemporaryDisable : IDisposable
125-
{
126-
public static IDisposable None { get; } = new NoOpDisposable();
127-
128-
private readonly Action _onDispose;
129-
130-
public TemporaryDisable(Action onDispose)
131-
{
132-
_onDispose = onDispose;
133-
}
134-
135-
#region IDisposable Members
136-
137-
/// <inheritdoc cref="IDisposable.Dispose()" />
138-
public void Dispose() => _onDispose();
139-
140-
#endregion
141-
}
14281
}

Source/Testably.Abstractions.Testing/Statistics/IFileSystemStatistics.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,9 @@ public interface IFileSystemStatistics
4444
/// Statistical information about calls to <see cref="IFileSystem.Path" />.
4545
/// </summary>
4646
IStatistics<IPath> Path { get; }
47+
48+
/// <summary>
49+
/// The sum of all registered statistic calls.
50+
/// </summary>
51+
int TotalCount { get; }
4752
}

Source/Testably.Abstractions.Testing/Storage/InMemoryContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public IStorageAccessHandle RequestAccess(FileAccess access, FileShare share,
148148
bool ignoreMetadataErrors = true,
149149
int? hResult = null)
150150
{
151-
if (_fileSystem.StatisticsRegistration.IsInitializing())
151+
if (_fileSystem.Registration.IsInitializing())
152152
{
153153
return FileHandle.Ignore;
154154
}

Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ namespace Testably.Abstractions.Testing.Statistics
294294
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileStreamFactory, System.IO.Abstractions.FileSystemStream> FileStream { get; }
295295
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileSystemWatcherFactory, System.IO.Abstractions.IFileSystemWatcher> FileSystemWatcher { get; }
296296
Testably.Abstractions.Testing.Statistics.IStatistics<System.IO.Abstractions.IPath> Path { get; }
297+
int TotalCount { get; }
297298
}
298299
public interface IPathStatistics<TFactory, TType> : Testably.Abstractions.Testing.Statistics.IStatistics, Testably.Abstractions.Testing.Statistics.IStatistics<TFactory>
299300
{

Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net7.0.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ namespace Testably.Abstractions.Testing.Statistics
294294
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileStreamFactory, System.IO.Abstractions.FileSystemStream> FileStream { get; }
295295
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileSystemWatcherFactory, System.IO.Abstractions.IFileSystemWatcher> FileSystemWatcher { get; }
296296
Testably.Abstractions.Testing.Statistics.IStatistics<System.IO.Abstractions.IPath> Path { get; }
297+
int TotalCount { get; }
297298
}
298299
public interface IPathStatistics<TFactory, TType> : Testably.Abstractions.Testing.Statistics.IStatistics, Testably.Abstractions.Testing.Statistics.IStatistics<TFactory>
299300
{

Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ namespace Testably.Abstractions.Testing.Statistics
292292
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileStreamFactory, System.IO.Abstractions.FileSystemStream> FileStream { get; }
293293
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileSystemWatcherFactory, System.IO.Abstractions.IFileSystemWatcher> FileSystemWatcher { get; }
294294
Testably.Abstractions.Testing.Statistics.IStatistics<System.IO.Abstractions.IPath> Path { get; }
295+
int TotalCount { get; }
295296
}
296297
public interface IPathStatistics<TFactory, TType> : Testably.Abstractions.Testing.Statistics.IStatistics, Testably.Abstractions.Testing.Statistics.IStatistics<TFactory>
297298
{

Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ namespace Testably.Abstractions.Testing.Statistics
291291
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileStreamFactory, System.IO.Abstractions.FileSystemStream> FileStream { get; }
292292
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileSystemWatcherFactory, System.IO.Abstractions.IFileSystemWatcher> FileSystemWatcher { get; }
293293
Testably.Abstractions.Testing.Statistics.IStatistics<System.IO.Abstractions.IPath> Path { get; }
294+
int TotalCount { get; }
294295
}
295296
public interface IPathStatistics<TFactory, TType> : Testably.Abstractions.Testing.Statistics.IStatistics, Testably.Abstractions.Testing.Statistics.IStatistics<TFactory>
296297
{

Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ namespace Testably.Abstractions.Testing.Statistics
291291
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileStreamFactory, System.IO.Abstractions.FileSystemStream> FileStream { get; }
292292
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileSystemWatcherFactory, System.IO.Abstractions.IFileSystemWatcher> FileSystemWatcher { get; }
293293
Testably.Abstractions.Testing.Statistics.IStatistics<System.IO.Abstractions.IPath> Path { get; }
294+
int TotalCount { get; }
294295
}
295296
public interface IPathStatistics<TFactory, TType> : Testably.Abstractions.Testing.Statistics.IStatistics, Testably.Abstractions.Testing.Statistics.IStatistics<TFactory>
296297
{

Tests/Testably.Abstractions.Testing.Tests/FileSystemInitializer/DirectoryCleanerTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public void InitializeBasePath_ShouldCreateDirectoryAndLogBasePath()
113113
using IDirectoryCleaner directoryCleaner =
114114
sut.SetCurrentDirectoryToEmptyTemporaryDirectory(logger: t => receivedLogs.Add(t));
115115

116-
sut.StatisticsRegistration.TotalCount.Should().Be(0);
116+
sut.Statistics.TotalCount.Should().Be(0);
117117
string currentDirectory = sut.Directory.GetCurrentDirectory();
118118
sut.Should().HaveDirectory(currentDirectory);
119119
receivedLogs.Should().Contain(m => m.Contains($"'{currentDirectory}'"));

0 commit comments

Comments
 (0)