Skip to content

Commit

Permalink
feat: Add TotalCount to Statistics (#604)
Browse files Browse the repository at this point in the history
* Add TotalCount to Statistics

* Move FileSystemRegistration to "Helpers" namespace
  • Loading branch information
vbreuss authored May 19, 2024
1 parent 31315ac commit 9912392
Show file tree
Hide file tree
Showing 28 changed files with 537 additions and 502 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ internal static IDisposable IgnoreStatistics(this IFileSystem fileSystem)
{
if (fileSystem is MockFileSystem mockFileSystem)
{
return mockFileSystem.StatisticsRegistration.Ignore();
return mockFileSystem.Registration.Ignore();
}

return new NoOpDisposable();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Threading;
using Testably.Abstractions.Testing.Statistics;

namespace Testably.Abstractions.Testing.Helpers;

internal sealed class FileSystemRegistration : IStatisticsGate
{
private static readonly AsyncLocal<bool> IsDisabled = new();
private static readonly AsyncLocal<bool> IsInit = new();

/// <summary>
/// The total count of registered statistic calls.
/// </summary>
public int TotalCount => _counter;

private int _counter;

#region IStatisticsGate Members

/// <inheritdoc cref="IStatisticsGate.GetCounter()" />
public int GetCounter()
{
return Interlocked.Increment(ref _counter);
}

/// <inheritdoc cref="IStatisticsGate.TryGetLock(out IDisposable)" />
public bool TryGetLock(out IDisposable release)
{
if (IsDisabled.Value)
{
release = TemporaryDisable.None;
return false;
}

IsDisabled.Value = true;
release = new TemporaryDisable(() => IsDisabled.Value = false);
return true;
}

#endregion

/// <summary>
/// Ignores all registrations until the return value is disposed.
/// </summary>
internal IDisposable Ignore()
{
if (IsDisabled.Value)
{
return TemporaryDisable.None;
}

IsDisabled.Value = true;
IsInit.Value = true;
return new TemporaryDisable(() =>
{
IsDisabled.Value = false;
IsInit.Value = false;
});
}

internal bool IsInitializing()
=> IsInit.Value;

private sealed class TemporaryDisable : IDisposable
{
public static IDisposable None { get; } = new NoOpDisposable();

private readonly Action _onDispose;

public TemporaryDisable(Action onDispose)
{
_onDispose = onDispose;
}

#region IDisposable Members

/// <inheritdoc cref="IDisposable.Dispose()" />
public void Dispose() => _onDispose();

#endregion
}
}
7 changes: 5 additions & 2 deletions Source/Testably.Abstractions.Testing/MockFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ internal ISafeFileHandleStrategy SafeFileHandleStrategy
private set;
}

internal FileSystemStatistics StatisticsRegistration { get; }

/// <summary>
/// The underlying storage of directories and files.
/// </summary>
Expand All @@ -91,7 +93,7 @@ internal ISafeFileHandleStrategy SafeFileHandleStrategy
internal IReadOnlyList<IStorageContainer> StorageContainers
=> _storage.GetContainers();

internal readonly FileSystemStatistics StatisticsRegistration;
internal FileSystemRegistration Registration { get; }

private readonly DirectoryMock _directoryMock;
private readonly FileMock _fileMock;
Expand Down Expand Up @@ -120,8 +122,9 @@ public MockFileSystem(Func<MockFileSystemOptions, MockFileSystemOptions> options
SimulationMode = SimulationMode.Native;
Execute = new Execute(this);
#endif
Registration = new FileSystemRegistration();
StatisticsRegistration = new FileSystemStatistics(this);
using IDisposable release = StatisticsRegistration.Ignore();
using IDisposable release = Registration.Ignore();
RandomSystem =
new MockRandomSystem(initialization.RandomProvider ?? RandomProvider.Default());
TimeSystem = new MockTimeSystem(TimeProvider.Now());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
using System;
using System.Threading;
using Testably.Abstractions.Testing.Helpers;
namespace Testably.Abstractions.Testing.Statistics;

namespace Testably.Abstractions.Testing.Statistics;

internal sealed class FileSystemStatistics : IFileSystemStatistics, IStatisticsGate
internal sealed class FileSystemStatistics : IFileSystemStatistics
{
private static readonly AsyncLocal<bool> IsDisabled = new();
private static readonly AsyncLocal<bool> IsInit = new();

/// <summary>
/// The total count of registered statistic calls.
/// </summary>
public int TotalCount => _counter;

internal readonly CallStatistics<IDirectory> Directory;
internal readonly PathStatistics<IDirectoryInfoFactory, IDirectoryInfo> DirectoryInfo;
internal readonly PathStatistics<IDriveInfoFactory, IDriveInfo> DriveInfo;
Expand All @@ -25,118 +13,69 @@ internal readonly PathStatistics<IFileSystemWatcherFactory, IFileSystemWatcher>
FileSystemWatcher;

internal readonly CallStatistics<IPath> Path;
private int _counter;
private readonly MockFileSystem _fileSystem;

public FileSystemStatistics(MockFileSystem fileSystem)
{
Directory = new CallStatistics<IDirectory>(this, nameof(IFileSystem.Directory));
_fileSystem = fileSystem;
IStatisticsGate statisticsGate = fileSystem.Registration;

Directory = new CallStatistics<IDirectory>(
statisticsGate, nameof(IFileSystem.Directory));
DirectoryInfo = new PathStatistics<IDirectoryInfoFactory, IDirectoryInfo>(
this, fileSystem, nameof(IFileSystem.DirectoryInfo));
statisticsGate, fileSystem, nameof(IFileSystem.DirectoryInfo));
DriveInfo = new PathStatistics<IDriveInfoFactory, IDriveInfo>(
this, fileSystem, nameof(IFileSystem.DriveInfo));
File = new CallStatistics<IFile>(this, nameof(IFileSystem.File));
statisticsGate, fileSystem, nameof(IFileSystem.DriveInfo));
File = new CallStatistics<IFile>(
statisticsGate, nameof(IFileSystem.File));
FileInfo = new PathStatistics<IFileInfoFactory, IFileInfo>(
this, fileSystem, nameof(IFileSystem.FileInfo));
statisticsGate, fileSystem, nameof(IFileSystem.FileInfo));
FileStream = new PathStatistics<IFileStreamFactory, FileSystemStream>(
this, fileSystem, nameof(IFileSystem.FileStream));
statisticsGate, fileSystem, nameof(IFileSystem.FileStream));
FileSystemWatcher = new PathStatistics<IFileSystemWatcherFactory, IFileSystemWatcher>(
this, fileSystem, nameof(IFileSystem.FileSystemWatcher));
Path = new CallStatistics<IPath>(this, nameof(IFileSystem.Path));
statisticsGate, fileSystem, nameof(IFileSystem.FileSystemWatcher));
Path = new CallStatistics<IPath>(
statisticsGate, nameof(IFileSystem.Path));
}

#region IFileSystemStatistics Members

/// <inheritdoc cref="IFileSystemStatistics.TotalCount" />
public int TotalCount
=> _fileSystem.Registration.TotalCount;

/// <inheritdoc cref="IFileSystemStatistics.Directory" />
IStatistics<IDirectory> IFileSystemStatistics.Directory => Directory;
IStatistics<IDirectory> IFileSystemStatistics.Directory
=> Directory;

/// <inheritdoc cref="IFileSystemStatistics.DirectoryInfo" />
IPathStatistics<IDirectoryInfoFactory, IDirectoryInfo> IFileSystemStatistics.DirectoryInfo
=> DirectoryInfo;

/// <inheritdoc cref="IFileSystemStatistics.DriveInfo" />
IPathStatistics<IDriveInfoFactory, IDriveInfo> IFileSystemStatistics.DriveInfo => DriveInfo;
IPathStatistics<IDriveInfoFactory, IDriveInfo> IFileSystemStatistics.DriveInfo
=> DriveInfo;

/// <inheritdoc cref="IFileSystemStatistics.File" />
IStatistics<IFile> IFileSystemStatistics.File => File;
IStatistics<IFile> IFileSystemStatistics.File
=> File;

/// <inheritdoc cref="IFileSystemStatistics.FileInfo" />
IPathStatistics<IFileInfoFactory, IFileInfo> IFileSystemStatistics.FileInfo => FileInfo;
IPathStatistics<IFileInfoFactory, IFileInfo> IFileSystemStatistics.FileInfo
=> FileInfo;

/// <inheritdoc cref="IFileSystemStatistics.FileStream" />
IPathStatistics<IFileStreamFactory, FileSystemStream> IFileSystemStatistics.FileStream
=> FileStream;

/// <inheritdoc cref="IFileSystemStatistics.FileSystemWatcher" />
IPathStatistics<IFileSystemWatcherFactory, IFileSystemWatcher> IFileSystemStatistics.
FileSystemWatcher => FileSystemWatcher;
IPathStatistics<IFileSystemWatcherFactory, IFileSystemWatcher>
IFileSystemStatistics.FileSystemWatcher
=> FileSystemWatcher;

/// <inheritdoc cref="IFileSystemStatistics.Path" />
IStatistics<IPath> IFileSystemStatistics.Path => Path;

#endregion

#region IStatisticsGate Members

/// <inheritdoc cref="IStatisticsGate.GetCounter()" />
public int GetCounter()
{
return Interlocked.Increment(ref _counter);
}

/// <inheritdoc cref="IStatisticsGate.TryGetLock(out IDisposable)" />
public bool TryGetLock(out IDisposable release)
{
if (IsDisabled.Value)
{
release = TemporaryDisable.None;
return false;
}

IsDisabled.Value = true;
release = new TemporaryDisable(() => IsDisabled.Value = false);
return true;
}
IStatistics<IPath> IFileSystemStatistics.Path
=> Path;

#endregion

/// <summary>
/// Ignores all registrations until the return value is disposed.
/// </summary>
internal IDisposable Ignore()
{
if (IsDisabled.Value)
{
return TemporaryDisable.None;
}

IsDisabled.Value = true;
IsInit.Value = true;
return new TemporaryDisable(() =>
{
IsDisabled.Value = false;
IsInit.Value = false;
});
}

internal bool IsInitializing()
=> IsInit.Value;

private sealed class TemporaryDisable : IDisposable
{
public static IDisposable None { get; } = new NoOpDisposable();

private readonly Action _onDispose;

public TemporaryDisable(Action onDispose)
{
_onDispose = onDispose;
}

#region IDisposable Members

/// <inheritdoc cref="IDisposable.Dispose()" />
public void Dispose() => _onDispose();

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@ public interface IFileSystemStatistics
/// Statistical information about calls to <see cref="IFileSystem.Path" />.
/// </summary>
IStatistics<IPath> Path { get; }

/// <summary>
/// The sum of all registered statistic calls.
/// </summary>
int TotalCount { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public IStorageAccessHandle RequestAccess(FileAccess access, FileShare share,
bool ignoreMetadataErrors = true,
int? hResult = null)
{
if (_fileSystem.StatisticsRegistration.IsInitializing())
if (_fileSystem.Registration.IsInitializing())
{
return FileHandle.Ignore;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ namespace Testably.Abstractions.Testing.Statistics
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileStreamFactory, System.IO.Abstractions.FileSystemStream> FileStream { get; }
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileSystemWatcherFactory, System.IO.Abstractions.IFileSystemWatcher> FileSystemWatcher { get; }
Testably.Abstractions.Testing.Statistics.IStatistics<System.IO.Abstractions.IPath> Path { get; }
int TotalCount { get; }
}
public interface IPathStatistics<TFactory, TType> : Testably.Abstractions.Testing.Statistics.IStatistics, Testably.Abstractions.Testing.Statistics.IStatistics<TFactory>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ namespace Testably.Abstractions.Testing.Statistics
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileStreamFactory, System.IO.Abstractions.FileSystemStream> FileStream { get; }
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileSystemWatcherFactory, System.IO.Abstractions.IFileSystemWatcher> FileSystemWatcher { get; }
Testably.Abstractions.Testing.Statistics.IStatistics<System.IO.Abstractions.IPath> Path { get; }
int TotalCount { get; }
}
public interface IPathStatistics<TFactory, TType> : Testably.Abstractions.Testing.Statistics.IStatistics, Testably.Abstractions.Testing.Statistics.IStatistics<TFactory>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ namespace Testably.Abstractions.Testing.Statistics
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileStreamFactory, System.IO.Abstractions.FileSystemStream> FileStream { get; }
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileSystemWatcherFactory, System.IO.Abstractions.IFileSystemWatcher> FileSystemWatcher { get; }
Testably.Abstractions.Testing.Statistics.IStatistics<System.IO.Abstractions.IPath> Path { get; }
int TotalCount { get; }
}
public interface IPathStatistics<TFactory, TType> : Testably.Abstractions.Testing.Statistics.IStatistics, Testably.Abstractions.Testing.Statistics.IStatistics<TFactory>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ namespace Testably.Abstractions.Testing.Statistics
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileStreamFactory, System.IO.Abstractions.FileSystemStream> FileStream { get; }
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileSystemWatcherFactory, System.IO.Abstractions.IFileSystemWatcher> FileSystemWatcher { get; }
Testably.Abstractions.Testing.Statistics.IStatistics<System.IO.Abstractions.IPath> Path { get; }
int TotalCount { get; }
}
public interface IPathStatistics<TFactory, TType> : Testably.Abstractions.Testing.Statistics.IStatistics, Testably.Abstractions.Testing.Statistics.IStatistics<TFactory>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ namespace Testably.Abstractions.Testing.Statistics
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileStreamFactory, System.IO.Abstractions.FileSystemStream> FileStream { get; }
Testably.Abstractions.Testing.Statistics.IPathStatistics<System.IO.Abstractions.IFileSystemWatcherFactory, System.IO.Abstractions.IFileSystemWatcher> FileSystemWatcher { get; }
Testably.Abstractions.Testing.Statistics.IStatistics<System.IO.Abstractions.IPath> Path { get; }
int TotalCount { get; }
}
public interface IPathStatistics<TFactory, TType> : Testably.Abstractions.Testing.Statistics.IStatistics, Testably.Abstractions.Testing.Statistics.IStatistics<TFactory>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public void InitializeBasePath_ShouldCreateDirectoryAndLogBasePath()
using IDirectoryCleaner directoryCleaner =
sut.SetCurrentDirectoryToEmptyTemporaryDirectory(logger: t => receivedLogs.Add(t));

sut.StatisticsRegistration.TotalCount.Should().Be(0);
sut.Statistics.TotalCount.Should().Be(0);
string currentDirectory = sut.Directory.GetCurrentDirectory();
sut.Should().HaveDirectory(currentDirectory);
receivedLogs.Should().Contain(m => m.Contains($"'{currentDirectory}'"));
Expand Down
Loading

0 comments on commit 9912392

Please sign in to comment.