From 7474abc11d881b5bbd36bb536ffd9fc2aced4680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Sun, 10 Mar 2024 16:01:02 +0100 Subject: [PATCH] refactor: replace the dictionary in the statistics with an array (#478) 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. --- .../Statistics/CallStatistics.cs | 12 +--- .../Statistics/IStatistics.cs | 2 +- .../Statistics/MethodStatistic.cs | 8 ++- .../FileSystem/FileStreamStatisticsTests.cs | 8 +-- .../Statistics/MethodStatisticsTests.cs | 8 ++- .../Statistics/StatisticsTests.cs | 33 ++++++--- .../TestHelpers/StatisticsTestHelpers.cs | 68 +++++++++---------- 7 files changed, 77 insertions(+), 62 deletions(-) diff --git a/Source/Testably.Abstractions.Testing/Statistics/CallStatistics.cs b/Source/Testably.Abstractions.Testing/Statistics/CallStatistics.cs index aa84a2c6a..5232e3d6c 100644 --- a/Source/Testably.Abstractions.Testing/Statistics/CallStatistics.cs +++ b/Source/Testably.Abstractions.Testing/Statistics/CallStatistics.cs @@ -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 _calls = new(); - public IReadOnlyDictionary Methods -#if NET7_0_OR_GREATER - => _calls.AsReadOnly(); -#else - => _calls; -#endif + private readonly ConcurrentQueue _calls = new(); + public MethodStatistic[] Methods => _calls.ToArray(); public CallStatistics(IStatisticsGate statisticsGate) { @@ -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; diff --git a/Source/Testably.Abstractions.Testing/Statistics/IStatistics.cs b/Source/Testably.Abstractions.Testing/Statistics/IStatistics.cs index 10955e85a..b0ca8e41f 100644 --- a/Source/Testably.Abstractions.Testing/Statistics/IStatistics.cs +++ b/Source/Testably.Abstractions.Testing/Statistics/IStatistics.cs @@ -10,5 +10,5 @@ public interface IStatistics /// /// Lists all called mocked methods. /// - IReadOnlyDictionary Methods { get; } + MethodStatistic[] Methods { get; } } diff --git a/Source/Testably.Abstractions.Testing/Statistics/MethodStatistic.cs b/Source/Testably.Abstractions.Testing/Statistics/MethodStatistic.cs index 653151911..805a40b25 100644 --- a/Source/Testably.Abstractions.Testing/Statistics/MethodStatistic.cs +++ b/Source/Testably.Abstractions.Testing/Statistics/MethodStatistic.cs @@ -7,6 +7,11 @@ namespace Testably.Abstractions.Testing.Statistics; /// public sealed class MethodStatistic { + /// + /// The global counter value to determine the order of calls. + /// + public int Counter { get; } + /// /// The name of the called method. /// @@ -17,8 +22,9 @@ public sealed class MethodStatistic /// public ParameterDescription[] Parameters { get; } - internal MethodStatistic(string name, ParameterDescription[] parameters) + internal MethodStatistic(int counter, string name, ParameterDescription[] parameters) { + Counter = counter; Name = name; Parameters = parameters; } diff --git a/Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/FileStreamStatisticsTests.cs b/Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/FileStreamStatisticsTests.cs index ce069a338..daa4dc766 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/FileStreamStatisticsTests.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/Statistics/FileSystem/FileStreamStatisticsTests.cs @@ -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)); @@ -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)); diff --git a/Tests/Testably.Abstractions.Testing.Tests/Statistics/MethodStatisticsTests.cs b/Tests/Testably.Abstractions.Testing.Tests/Statistics/MethodStatisticsTests.cs index eb114777b..8172ccc68 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/Statistics/MethodStatisticsTests.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/Statistics/MethodStatisticsTests.cs @@ -1,4 +1,6 @@ -namespace Testably.Abstractions.Testing.Tests.Statistics; +using System.Linq; + +namespace Testably.Abstractions.Testing.Tests.Statistics; public sealed class MethodStatisticsTests { @@ -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 @@ -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 diff --git a/Tests/Testably.Abstractions.Testing.Tests/Statistics/StatisticsTests.cs b/Tests/Testably.Abstractions.Testing.Tests/Statistics/StatisticsTests.cs index 89cf696d6..d2a90cb5d 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/Statistics/StatisticsTests.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/Statistics/StatisticsTests.cs @@ -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; @@ -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)); } @@ -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(); } } @@ -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(); } } @@ -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(m => + m.Name == nameof(IDirectory.CreateDirectory) && + m.Counter == 1); + sut.Statistics.File.Methods.First() + .Should().Match(m => + m.Name == nameof(IFile.WriteAllText) && + m.Counter == 2); + sut.Statistics.FileInfo.Methods.First() + .Should().Match(m => + m.Name == nameof(IFileInfoFactory.New) && + m.Counter == 3); + sut.Statistics.FileInfo["bar.txt"].Methods.First() + .Should().Match(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] diff --git a/Tests/Testably.Abstractions.Testing.Tests/TestHelpers/StatisticsTestHelpers.cs b/Tests/Testably.Abstractions.Testing.Tests/TestHelpers/StatisticsTestHelpers.cs index 1e50837f3..ed1f8f913 100644 --- a/Tests/Testably.Abstractions.Testing.Tests/TestHelpers/StatisticsTestHelpers.cs +++ b/Tests/Testably.Abstractions.Testing.Tests/TestHelpers/StatisticsTestHelpers.cs @@ -8,8 +8,8 @@ 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); @@ -17,8 +17,8 @@ public static void ShouldOnlyContain(this IStatistics statistics, string name, 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); } @@ -26,8 +26,8 @@ public static void ShouldOnlyContain(this IStatistics statistics, string name) public static void ShouldOnlyContain(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)); @@ -36,8 +36,8 @@ public static void ShouldOnlyContain(this IStatistics statistics, string nam public static void ShouldOnlyContain(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)); @@ -47,8 +47,8 @@ public static void ShouldOnlyContain(this IStatistics statistics, string nam public static void ShouldOnlyContain(this IStatistics statistics, string name, ReadOnlySpan 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(); @@ -57,8 +57,8 @@ public static void ShouldOnlyContain(this IStatistics statistics, string nam public static void ShouldOnlyContain(this IStatistics statistics, string name, ReadOnlySpan parameter1, ReadOnlySpan 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(); @@ -68,8 +68,8 @@ public static void ShouldOnlyContain(this IStatistics statistics, string public static void ShouldOnlyContain(this IStatistics statistics, string name, ReadOnlySpan parameter1, ReadOnlySpan parameter2, ReadOnlySpan 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(); @@ -80,8 +80,8 @@ public static void ShouldOnlyContain(this IStatistics statistics, st public static void ShouldOnlyContain(this IStatistics statistics, string name, ReadOnlySpan parameter1, ReadOnlySpan parameter2, ReadOnlySpan parameter3, ReadOnlySpan 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(); @@ -93,8 +93,8 @@ public static void ShouldOnlyContain(this IStatistics statistics public static void ShouldOnlyContain(this IStatistics statistics, string name, ReadOnlySpan parameter1, ReadOnlySpan 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(); @@ -106,8 +106,8 @@ public static void ShouldOnlyContain(this IStatistics statistics public static void ShouldOnlyContain(this IStatistics statistics, string name, ReadOnlySpan parameter1, ReadOnlySpan parameter2, Span 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(); @@ -119,8 +119,8 @@ public static void ShouldOnlyContain(this IStatistics statistics public static void ShouldOnlyContain(this IStatistics statistics, string name, ReadOnlySpan parameter1, ReadOnlySpan parameter2, ReadOnlySpan parameter3, Span 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(); @@ -133,8 +133,8 @@ public static void ShouldOnlyContain(this IStatistics statis public static void ShouldOnlyContain(this IStatistics statistics, string name, Span 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(); @@ -144,8 +144,8 @@ public static void ShouldOnlyContain(this IStatistics statistics, string nam public static void ShouldOnlyContain(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) && @@ -155,8 +155,8 @@ public static void ShouldOnlyContain(this IStatistics statistics, string public static void ShouldOnlyContain(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) && @@ -167,8 +167,8 @@ public static void ShouldOnlyContain(this IStatistics statistics, st public static void ShouldOnlyContain(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) && @@ -180,8 +180,8 @@ public static void ShouldOnlyContain(this IStatistics statistics public static void ShouldOnlyContain(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) && @@ -195,8 +195,8 @@ public static void ShouldOnlyContain(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) &&