Skip to content

Commit

Permalink
refactor: replace the dictionary in the statistics with an array (#478)
Browse files Browse the repository at this point in the history
The global counter should not be treated as an index!

Therefore replace the dictionary in the statistics with an array and
move the counter information into the `MethodStatistic` itself.
  • Loading branch information
vbreuss authored Mar 10, 2024
1 parent b706272 commit 7474abc
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace Testably.Abstractions.Testing.Statistics;

internal class CallStatistics : IStatistics
{
private readonly IStatisticsGate _statisticsGate;
private readonly ConcurrentDictionary<int, MethodStatistic> _calls = new();
public IReadOnlyDictionary<int, MethodStatistic> Methods
#if NET7_0_OR_GREATER
=> _calls.AsReadOnly();
#else
=> _calls;
#endif
private readonly ConcurrentQueue<MethodStatistic> _calls = new();
public MethodStatistic[] Methods => _calls.ToArray();

public CallStatistics(IStatisticsGate statisticsGate)
{
Expand All @@ -29,7 +23,7 @@ internal IDisposable Register(string name, params ParameterDescription[] paramet
if (_statisticsGate.TryGetLock(out IDisposable release))
{
int counter = _statisticsGate.GetCounter();
_calls[counter] = new MethodStatistic(name, parameters);
_calls.Enqueue(new MethodStatistic(counter, name, parameters));
}

return release;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ public interface IStatistics
/// <summary>
/// Lists all called mocked methods.
/// </summary>
IReadOnlyDictionary<int, MethodStatistic> Methods { get; }
MethodStatistic[] Methods { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ namespace Testably.Abstractions.Testing.Statistics;
/// </summary>
public sealed class MethodStatistic
{
/// <summary>
/// The global counter value to determine the order of calls.
/// </summary>
public int Counter { get; }

/// <summary>
/// The name of the called method.
/// </summary>
Expand All @@ -17,8 +22,9 @@ public sealed class MethodStatistic
/// </summary>
public ParameterDescription[] Parameters { get; }

internal MethodStatistic(string name, ParameterDescription[] parameters)
internal MethodStatistic(int counter, string name, ParameterDescription[] parameters)
{
Counter = counter;
Name = name;
Parameters = parameters;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ public void EndRead_IAsyncResult_ShouldRegisterCall()

fileStream.EndRead(asyncResult);

sut.Statistics.FileStream["foo"].Methods.Count.Should().Be(2);
sut.Statistics.FileStream["foo"].Methods.Values.Should()
sut.Statistics.FileStream["foo"].Methods.Length.Should().Be(2);
sut.Statistics.FileStream["foo"].Methods.Should()
.ContainSingle(c => c.Name == nameof(FileSystemStream.EndRead) &&
c.Parameters.Length == 1 &&
c.Parameters[0].Is(asyncResult));
Expand All @@ -98,8 +98,8 @@ public void EndWrite_IAsyncResult_ShouldRegisterCall()

fileStream.EndWrite(asyncResult);

sut.Statistics.FileStream["foo"].Methods.Count.Should().Be(2);
sut.Statistics.FileStream["foo"].Methods.Values.Should()
sut.Statistics.FileStream["foo"].Methods.Length.Should().Be(2);
sut.Statistics.FileStream["foo"].Methods.Should()
.ContainSingle(c => c.Name == nameof(FileSystemStream.EndWrite) &&
c.Parameters.Length == 1 &&
c.Parameters[0].Is(asyncResult));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Testably.Abstractions.Testing.Tests.Statistics;
using System.Linq;

namespace Testably.Abstractions.Testing.Tests.Statistics;

public sealed class MethodStatisticsTests
{
Expand All @@ -8,7 +10,7 @@ public void ToString_ShouldContainName()
MockFileSystem sut = new();
sut.Directory.CreateDirectory("foo");

string result = sut.Statistics.Directory.Methods[1].ToString();
string result = sut.Statistics.Directory.Methods.First().ToString();

result.Should()
.Contain(nameof(IDirectory.CreateDirectory)).And
Expand All @@ -22,7 +24,7 @@ public void ToString_ShouldContainParameters()
MockFileSystem sut = new();
sut.File.WriteAllText("foo", "bar");

string result = sut.Statistics.File.Methods[1].ToString();
string result = sut.Statistics.File.Methods.First().ToString();

result.Should()
.Contain(nameof(IFile.WriteAllText)).And
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Testably.Abstractions.Testing.Statistics;
using Testably.Abstractions.Testing.Tests.Statistics.FileSystem;
using Testably.Abstractions.Testing.Tests.TestHelpers;

Expand Down Expand Up @@ -37,14 +38,14 @@ public async Task Statistics_ShouldSupportParallelCalls()

foreach (string directory in directories)
{
sut.Statistics.Directory.Methods.Values
sut.Statistics.Directory.Methods
.Should().ContainSingle(x =>
x.Name == nameof(Directory.CreateDirectory) &&
x.Parameters.Length == 1 &&
x.Parameters[0].Is(directory));
}

sut.Statistics.Directory.Methods.Keys.Should()
sut.Statistics.Directory.Methods.Select(x => x.Counter).Should()
.BeEquivalentTo(Enumerable.Range(1, directories.Length));
}

Expand All @@ -63,7 +64,7 @@ public void Statistics_ShouldIncrementCallOrder()

for (int i = 0; i < directories.Length; i++)
{
sut.Statistics.Directory.Methods[i + 1]
sut.Statistics.Directory.Methods[i]
.Parameters[0].Is(directories[i]).Should().BeTrue();
}
}
Expand All @@ -84,10 +85,10 @@ public void Statistics_ShouldKeepCallOrder()
for (int i = 0; i < directories.Length; i++)
{
sut.Statistics.Directory.Methods
.OrderBy(x => x.Key)
.OrderBy(x => x.Counter)
.Skip(i)
.First()
.Value.Parameters[0].Is(directories[i]).Should().BeTrue();
.Parameters[0].Is(directories[i]).Should().BeTrue();
}
}

Expand All @@ -101,11 +102,23 @@ public void Statistics_ShouldUseGlobalIncrement()
using FileSystemStream stream = fileInfo.Open(FileMode.Open, FileAccess.Read);
_ = new StreamReader(stream).ReadToEnd();

sut.Statistics.Directory.Methods[1].Name.Should().Be(nameof(IDirectory.CreateDirectory));
sut.Statistics.File.Methods[2].Name.Should().Be(nameof(IFile.WriteAllText));
sut.Statistics.FileInfo.Methods[3].Name.Should().Be(nameof(IFileInfoFactory.New));
// Note: Index 4 ist used internally for creating the full path of the file info.
sut.Statistics.FileInfo["bar.txt"].Methods[5].Name.Should().Be(nameof(IFileInfo.Open));
sut.Statistics.Directory.Methods.First()
.Should().Match<MethodStatistic>(m =>
m.Name == nameof(IDirectory.CreateDirectory) &&
m.Counter == 1);
sut.Statistics.File.Methods.First()
.Should().Match<MethodStatistic>(m =>
m.Name == nameof(IFile.WriteAllText) &&
m.Counter == 2);
sut.Statistics.FileInfo.Methods.First()
.Should().Match<MethodStatistic>(m =>
m.Name == nameof(IFileInfoFactory.New) &&
m.Counter == 3);
sut.Statistics.FileInfo["bar.txt"].Methods.First()
.Should().Match<MethodStatistic>(m =>
m.Name == nameof(IFileInfo.Open) &&
// Note: Index 4 could be used internally for creating the full path of the file info.
m.Counter >= 4);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,26 @@ public static class StatisticsTestHelpers
public static void ShouldOnlyContain(this IStatistics statistics, string name,
object?[] parameters, string because = "")
{
statistics.Methods.Count.Should().Be(1, because);
statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1, because);
statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.SequenceEqual(parameters),
because);
}

public static void ShouldOnlyContain(this IStatistics statistics, string name)
{
statistics.Methods.Count.Should().Be(1);
statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 0);
}

public static void ShouldOnlyContain<T1>(this IStatistics statistics, string name,
T1 parameter1)
{
statistics.Methods.Count.Should().Be(1);
statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 1 &&
c.Parameters[0].Is(parameter1));
Expand All @@ -36,8 +36,8 @@ public static void ShouldOnlyContain<T1>(this IStatistics statistics, string nam
public static void ShouldOnlyContain<T1>(this IStatistics statistics, string name,
T1[] parameter1)
{
statistics.Methods.Count.Should().Be(1);
statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 1 &&
c.Parameters[0].Is(parameter1));
Expand All @@ -47,8 +47,8 @@ public static void ShouldOnlyContain<T1>(this IStatistics statistics, string nam
public static void ShouldOnlyContain<T1>(this IStatistics statistics, string name,
ReadOnlySpan<T1> parameter1)
{
statistics.Methods.Count.Should().Be(1);
ParameterDescription parameter = statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
ParameterDescription parameter = statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 1).Which.Parameters[0];
parameter.Is(parameter1).Should().BeTrue();
Expand All @@ -57,8 +57,8 @@ public static void ShouldOnlyContain<T1>(this IStatistics statistics, string nam
public static void ShouldOnlyContain<T1, T2>(this IStatistics statistics, string name,
ReadOnlySpan<T1> parameter1, ReadOnlySpan<T2> parameter2)
{
statistics.Methods.Count.Should().Be(1);
MethodStatistic? statistic = statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
MethodStatistic? statistic = statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 2).Which;
statistic.Parameters[0].Is(parameter1).Should().BeTrue();
Expand All @@ -68,8 +68,8 @@ public static void ShouldOnlyContain<T1, T2>(this IStatistics statistics, string
public static void ShouldOnlyContain<T1, T2, T3>(this IStatistics statistics, string name,
ReadOnlySpan<T1> parameter1, ReadOnlySpan<T2> parameter2, ReadOnlySpan<T3> parameter3)
{
statistics.Methods.Count.Should().Be(1);
MethodStatistic? statistic = statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
MethodStatistic? statistic = statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 3).Which;
statistic.Parameters[0].Is(parameter1).Should().BeTrue();
Expand All @@ -80,8 +80,8 @@ public static void ShouldOnlyContain<T1, T2, T3>(this IStatistics statistics, st
public static void ShouldOnlyContain<T1, T2, T3, T4>(this IStatistics statistics, string name,
ReadOnlySpan<T1> parameter1, ReadOnlySpan<T2> parameter2, ReadOnlySpan<T3> parameter3, ReadOnlySpan<T4> parameter4)
{
statistics.Methods.Count.Should().Be(1);
MethodStatistic? statistic = statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
MethodStatistic? statistic = statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 4).Which;
statistic.Parameters[0].Is(parameter1).Should().BeTrue();
Expand All @@ -93,8 +93,8 @@ public static void ShouldOnlyContain<T1, T2, T3, T4>(this IStatistics statistics
public static void ShouldOnlyContain<T1, T2, T3, T4>(this IStatistics statistics, string name,
ReadOnlySpan<T1> parameter1, ReadOnlySpan<T2> parameter2, T3 parameter3, T4 parameter4)
{
statistics.Methods.Count.Should().Be(1);
MethodStatistic? statistic = statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
MethodStatistic? statistic = statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 4).Which;
statistic.Parameters[0].Is(parameter1).Should().BeTrue();
Expand All @@ -106,8 +106,8 @@ public static void ShouldOnlyContain<T1, T2, T3, T4>(this IStatistics statistics
public static void ShouldOnlyContain<T1, T2, T3, T4>(this IStatistics statistics, string name,
ReadOnlySpan<T1> parameter1, ReadOnlySpan<T2> parameter2, Span<T3> parameter3, T4 parameter4)
{
statistics.Methods.Count.Should().Be(1);
MethodStatistic? statistic = statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
MethodStatistic? statistic = statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 4).Which;
statistic.Parameters[0].Is(parameter1).Should().BeTrue();
Expand All @@ -119,8 +119,8 @@ public static void ShouldOnlyContain<T1, T2, T3, T4>(this IStatistics statistics
public static void ShouldOnlyContain<T1, T2, T3, T4, T5>(this IStatistics statistics, string name,
ReadOnlySpan<T1> parameter1, ReadOnlySpan<T2> parameter2, ReadOnlySpan<T3> parameter3, Span<T4> parameter4, T5 parameter5)
{
statistics.Methods.Count.Should().Be(1);
MethodStatistic? statistic = statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
MethodStatistic? statistic = statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 5).Which;
statistic.Parameters[0].Is(parameter1).Should().BeTrue();
Expand All @@ -133,8 +133,8 @@ public static void ShouldOnlyContain<T1, T2, T3, T4, T5>(this IStatistics statis
public static void ShouldOnlyContain<T1>(this IStatistics statistics, string name,
Span<T1> parameter1)
{
statistics.Methods.Count.Should().Be(1);
ParameterDescription parameter = statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
ParameterDescription parameter = statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 1).Which.Parameters[0];
parameter.Is(parameter1).Should().BeTrue();
Expand All @@ -144,8 +144,8 @@ public static void ShouldOnlyContain<T1>(this IStatistics statistics, string nam
public static void ShouldOnlyContain<T1, T2>(this IStatistics statistics, string name,
T1 parameter1, T2 parameter2)
{
statistics.Methods.Count.Should().Be(1);
statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 2 &&
c.Parameters[0].Is(parameter1) &&
Expand All @@ -155,8 +155,8 @@ public static void ShouldOnlyContain<T1, T2>(this IStatistics statistics, string
public static void ShouldOnlyContain<T1, T2, T3>(this IStatistics statistics, string name,
T1 parameter1, T2 parameter2, T3 parameter3)
{
statistics.Methods.Count.Should().Be(1);
statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 3 &&
c.Parameters[0].Is(parameter1) &&
Expand All @@ -167,8 +167,8 @@ public static void ShouldOnlyContain<T1, T2, T3>(this IStatistics statistics, st
public static void ShouldOnlyContain<T1, T2, T3, T4>(this IStatistics statistics, string name,
T1 parameter1, T2 parameter2, T3 parameter3, T4 parameter4)
{
statistics.Methods.Count.Should().Be(1);
statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 4 &&
c.Parameters[0].Is(parameter1) &&
Expand All @@ -180,8 +180,8 @@ public static void ShouldOnlyContain<T1, T2, T3, T4>(this IStatistics statistics
public static void ShouldOnlyContain<T1, T2, T3, T4, T5>(this IStatistics statistics,
string name, T1 parameter1, T2 parameter2, T3 parameter3, T4 parameter4, T5 parameter5)
{
statistics.Methods.Count.Should().Be(1);
statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 5 &&
c.Parameters[0].Is(parameter1) &&
Expand All @@ -195,8 +195,8 @@ public static void ShouldOnlyContain<T1, T2, T3, T4, T5, T6>(this IStatistics st
string name, T1 parameter1, T2 parameter2, T3 parameter3, T4 parameter4, T5 parameter5,
T6 parameter6)
{
statistics.Methods.Count.Should().Be(1);
statistics.Methods.Values.Should()
statistics.Methods.Length.Should().Be(1);
statistics.Methods.Should()
.ContainSingle(c => c.Name == name &&
c.Parameters.Length == 6 &&
c.Parameters[0].Is(parameter1) &&
Expand Down

0 comments on commit 7474abc

Please sign in to comment.