From d652ba43e4ede8e9564d4dbfac79ec5a415e1599 Mon Sep 17 00:00:00 2001 From: gimlichael Date: Thu, 4 Jun 2026 15:55:39 +0200 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=94=84=20replace=20httpbin=20with=20m?= =?UTF-8?q?ockerapi=20in=20uri=20extensions=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs b/test/Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs index f47b1a94..2e433333 100644 --- a/test/Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs +++ b/test/Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Net.Http; using System.Threading; @@ -24,7 +24,7 @@ public UriExtensionsTest(ITestOutputHelper output) : base(output) public async Task HttpGetAsync_ShouldGetResponseFromUri() { // Test SlimHttpClientFactory robustness under parallel load using a reliable external server - var uri = new Uri("https://httpbin.org/status/200"); + var uri = new Uri("https://free.mockerapi.com/200"); var expected = 125; var atomicCount = 0; @@ -44,7 +44,7 @@ await ParallelFactory.ForAsync(0, expected, async (i, ct) => public async Task HttpGetAsync_ShouldHandleHttpStatusCodes() { // Test that the extension method properly returns non-OK status codes under parallel load - var uri = new Uri("https://httpbin.org/status/404"); + var uri = new Uri("https://free.mockerapi.com/404"); var expected = 50; var atomicCount = 0; @@ -60,4 +60,4 @@ await ParallelFactory.ForAsync(0, expected, async (i, ct) => Assert.Equal(expected, atomicCount); } } -} \ No newline at end of file +} From 1fbd04c032c96f11223c7c096ad74265455227b5 Mon Sep 17 00:00:00 2001 From: gimlichael Date: Thu, 4 Jun 2026 16:13:49 +0200 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9C=85=20add=20assertions=20for=20cache?= =?UTF-8?q?=20expiration=20and=20cleanup=20in=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SlimMemoryCacheTest.cs | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/test/Cuemon.Runtime.Caching.Tests/SlimMemoryCacheTest.cs b/test/Cuemon.Runtime.Caching.Tests/SlimMemoryCacheTest.cs index a55e5a91..d0bb57e1 100644 --- a/test/Cuemon.Runtime.Caching.Tests/SlimMemoryCacheTest.cs +++ b/test/Cuemon.Runtime.Caching.Tests/SlimMemoryCacheTest.cs @@ -25,6 +25,7 @@ public class SlimMemoryCacheTest : HostTest private const string Dependency60Namespace = "Dependency60"; private const int NumberOfItemsToCache = 1000; + private static readonly TimeSpan CleanupTimeout = TimeSpan.FromSeconds(15); public SlimMemoryCacheTest(ManagedHostFixture hostFixture, ITestOutputHelper output = null) : base(hostFixture, output) { @@ -230,19 +231,13 @@ public void Add_VerifyBothLogicalAndActualCacheRemovalUponExpirationForThirtySec { Thread.Sleep(TimeSpan.FromSeconds(30)); - Assert.Equal(0, _cache.Count(Dependency30Namespace)); - Assert.Equal(0, _cache.Count(Sliding30Namespace)); - Assert.Equal(0, _cache.Count(Absolute30Namespace)); + AssertNamespaceIsLogicallyExpired(Dependency30Namespace); + AssertNamespaceIsLogicallyExpired(Sliding30Namespace); + AssertNamespaceIsLogicallyExpired(Absolute30Namespace); - Assert.Equal(NumberOfItemsToCache, _cache.Where(pair => pair.Value.Namespace == Dependency30Namespace).ToList().Count); - Assert.Equal(NumberOfItemsToCache, _cache.Where(pair => pair.Value.Namespace == Sliding30Namespace).ToList().Count); - Assert.Equal(NumberOfItemsToCache, _cache.Where(pair => pair.Value.Namespace == Absolute30Namespace).ToList().Count); - - Thread.Sleep(TimeSpan.FromSeconds(10)); - - Assert.Equal(0, _cache.Where(pair => pair.Value.Namespace == Dependency30Namespace).ToList().Count); - Assert.Equal(0, _cache.Where(pair => pair.Value.Namespace == Sliding30Namespace).ToList().Count); - Assert.Equal(0, _cache.Where(pair => pair.Value.Namespace == Absolute30Namespace).ToList().Count); + AssertNamespaceIsPhysicallyRemoved(Dependency30Namespace); + AssertNamespaceIsPhysicallyRemoved(Sliding30Namespace); + AssertNamespaceIsPhysicallyRemoved(Absolute30Namespace); } [Fact, Priority(9)] @@ -250,19 +245,13 @@ public void Add_VerifyBothLogicalAndActualCacheRemovalUponExpirationForSixtySeco { Thread.Sleep(TimeSpan.FromSeconds(20)); - Assert.Equal(0, _cache.Count(Dependency60Namespace)); - Assert.Equal(0, _cache.Count(Sliding60Namespace)); - Assert.Equal(0, _cache.Count(Absolute60Namespace)); - - Assert.Equal(NumberOfItemsToCache, _cache.Where(pair => pair.Value.Namespace == Dependency60Namespace).ToList().Count); - Assert.Equal(NumberOfItemsToCache, _cache.Where(pair => pair.Value.Namespace == Sliding60Namespace).ToList().Count); - Assert.Equal(NumberOfItemsToCache, _cache.Where(pair => pair.Value.Namespace == Absolute60Namespace).ToList().Count); + AssertNamespaceIsLogicallyExpired(Dependency60Namespace); + AssertNamespaceIsLogicallyExpired(Sliding60Namespace); + AssertNamespaceIsLogicallyExpired(Absolute60Namespace); - Thread.Sleep(TimeSpan.FromSeconds(10)); - - Assert.Equal(0, _cache.Where(pair => pair.Value.Namespace == Dependency60Namespace).ToList().Count); - Assert.Equal(0, _cache.Where(pair => pair.Value.Namespace == Sliding60Namespace).ToList().Count); - Assert.Equal(0, _cache.Where(pair => pair.Value.Namespace == Absolute60Namespace).ToList().Count); + AssertNamespaceIsPhysicallyRemoved(Dependency60Namespace); + AssertNamespaceIsPhysicallyRemoved(Sliding60Namespace); + AssertNamespaceIsPhysicallyRemoved(Absolute60Namespace); } [Fact] @@ -329,5 +318,17 @@ public override void ConfigureServices(IServiceCollection services) }); services.AddSingleton(); } + + private void AssertNamespaceIsPhysicallyRemoved(string ns) + { + Assert.True(SpinWait.SpinUntil(() => !_cache.Any(pair => pair.Value.Namespace == ns), CleanupTimeout), + $"Cache entries in namespace '{ns}' were not physically removed within {CleanupTimeout}."); + } + + private void AssertNamespaceIsLogicallyExpired(string ns) + { + Assert.True(SpinWait.SpinUntil(() => _cache.Count(ns) == 0, CleanupTimeout), + $"Cache entries in namespace '{ns}' did not logically expire within {CleanupTimeout}."); + } } -} \ No newline at end of file +} From f613adad2aa72c6601a815b8b3ba367d1bd1f846 Mon Sep 17 00:00:00 2001 From: gimlichael Date: Thu, 4 Jun 2026 16:15:03 +0200 Subject: [PATCH 3/5] =?UTF-8?q?=E2=9C=85=20refactor=20time=20measure=20tes?= =?UTF-8?q?ts=20to=20use=20assert=20elapsed=20around=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TimeMeasureTest.cs | 96 ++++++++++--------- 1 file changed, 51 insertions(+), 45 deletions(-) diff --git a/test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs b/test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs index c6ba1678..d6e04aa7 100644 --- a/test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs +++ b/test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs @@ -11,19 +11,25 @@ namespace Cuemon.Diagnostics public class TimeMeasureTest : Test { private static readonly TimeSpan ExpectedExecutionTime = TimeSpan.FromSeconds(1); - private static readonly TimeSpan Jitter = TimeSpan.FromMilliseconds(250); + private static readonly TimeSpan LowerJitter = TimeSpan.FromMilliseconds(250); + private static readonly TimeSpan UpperJitter = TimeSpan.FromSeconds(2); public TimeMeasureTest(ITestOutputHelper output) : base(output) { } + private static void AssertElapsedAround(TimeSpan actual, TimeSpan expected) + { + Assert.InRange(actual, expected.Subtract(LowerJitter), expected.Add(UpperJitter)); + } + [Fact] public void WithAction_Use_0_Arguments_ShouldTakeAroundOneSecond() { var expected = ExpectedExecutionTime; var profiler = TimeMeasure.WithAction(() => Thread.Sleep(expected)); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.False(profiler.Member.HasParameters()); Assert.Empty(profiler.Data); @@ -37,7 +43,7 @@ public void WithAction_Use_1_Argument_ShouldTakeAroundOneSecond() var expected = ExpectedExecutionTime; var profiler = TimeMeasure.WithAction(a1 => Thread.Sleep(expected), 1); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Contains(profiler.Data.Values, o => o is int i && i == 1); @@ -52,7 +58,7 @@ public void WithAction_Use_2_Arguments_ShouldTakeAroundOneSecond() var expected = ExpectedExecutionTime; var profiler = TimeMeasure.WithAction((a1, a2) => Thread.Sleep(expected), 1, 2); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, i => Assert.Equal(1, i), i => Assert.Equal(2, i)); @@ -67,7 +73,7 @@ public void WithAction_Use_3_Arguments_ShouldTakeAroundOneSecond() var expected = ExpectedExecutionTime; var profiler = TimeMeasure.WithAction((a1, a2, a3) => Thread.Sleep(expected), 1, 2, 3); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -85,7 +91,7 @@ public void WithAction_Use_4_Arguments_ShouldTakeAroundOneSecond() var expected = ExpectedExecutionTime; var profiler = TimeMeasure.WithAction((a1, a2, a3, a4) => Thread.Sleep(expected), 1, 2, 3, 4); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -104,7 +110,7 @@ public void WithAction_Use_5_Arguments_ShouldTakeAroundOneSecond() var expected = ExpectedExecutionTime; var profiler = TimeMeasure.WithAction((a1, a2, a3, a4, a5) => Thread.Sleep(expected), 1, 2, 3, 4, 5); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -124,7 +130,7 @@ public void WithAction_Use_6_Arguments_ShouldTakeAroundOneSecond() var expected = ExpectedExecutionTime; var profiler = TimeMeasure.WithAction((a1, a2, a3, a4, a5, a6) => Thread.Sleep(expected), 1, 2, 3, 4, 5, 6); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -145,7 +151,7 @@ public void WithAction_Use_7_Arguments_ShouldTakeAroundOneSecond() var expected = ExpectedExecutionTime; var profiler = TimeMeasure.WithAction((a1, a2, a3, a4, a5, a6, a7) => Thread.Sleep(expected), 1, 2, 3, 4, 5, 6, 7); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -167,7 +173,7 @@ public void WithAction_Use_8_Arguments_ShouldTakeAroundOneSecond() var expected = ExpectedExecutionTime; var profiler = TimeMeasure.WithAction((a1, a2, a3, a4, a5, a6, a7, a8) => Thread.Sleep(expected), 1, 2, 3, 4, 5, 6, 7, 8); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -190,7 +196,7 @@ public void WithAction_Use_9_Arguments_ShouldTakeAroundOneSecond() var expected = ExpectedExecutionTime; var profiler = TimeMeasure.WithAction((a1, a2, a3, a4, a5, a6, a7, a8, a9) => Thread.Sleep(expected), 1, 2, 3, 4, 5, 6, 7, 8, 9); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -214,7 +220,7 @@ public void WithAction_Use_10_Arguments_ShouldTakeAroundOneSecond() var expected = ExpectedExecutionTime; var profiler = TimeMeasure.WithAction((a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) => Thread.Sleep(expected), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -245,7 +251,7 @@ public void WithFunc_Use_0_Arguments_ShouldTakeAroundOneSecond() }); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.False(profiler.Member.HasParameters()); Assert.Empty(profiler.Data); @@ -265,7 +271,7 @@ public void WithFunc_Use_1_Argument_ShouldTakeAroundOneSecond() }, 1); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -287,7 +293,7 @@ public void WithFunc_Use_2_Arguments_ShouldTakeAroundOneSecond() }, 1, 2); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -310,7 +316,7 @@ public void WithFunc_Use_3_Arguments_ShouldTakeAroundOneSecond() }, 1, 2, 3); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -334,7 +340,7 @@ public void WithFunc_Use_4_Arguments_ShouldTakeAroundOneSecond() }, 1, 2, 3, 4); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -359,7 +365,7 @@ public void WithFunc_Use_5_Arguments_ShouldTakeAroundOneSecond() }, 1, 2, 3, 4, 5); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -385,7 +391,7 @@ public void WithFunc_Use_6_Arguments_ShouldTakeAroundOneSecond() }, 1, 2, 3, 4, 5, 6); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -412,7 +418,7 @@ public void WithFunc_Use_7_Arguments_ShouldTakeAroundOneSecond() }, 1, 2, 3, 4, 5, 6, 7); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -440,7 +446,7 @@ public void WithFunc_Use_8_Arguments_ShouldTakeAroundOneSecond() }, 1, 2, 3, 4, 5, 6, 7, 8); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -469,7 +475,7 @@ public void WithFunc_Use_9_Arguments_ShouldTakeAroundOneSecond() }, 1, 2, 3, 4, 5, 6, 7, 8, 9); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -498,7 +504,7 @@ public void WithFunc_Use_10_Arguments_ShouldTakeAroundOneSecond() }, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.NotEmpty(profiler.Data); Assert.Collection(profiler.Data.Values, @@ -530,7 +536,7 @@ await Assert.ThrowsAnyAsync(async () => }); var profiler = await TimeMeasure.WithActionAsync(token => Task.Delay(expected, token), o => o.CancellationToken = ctsShouldPass.Token); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.Empty(profiler.Data); @@ -552,7 +558,7 @@ await Assert.ThrowsAnyAsync(async () => }); var profiler = await TimeMeasure.WithActionAsync((a, token) => Task.Delay(expected, token), 1, o => o.CancellationToken = ctsShouldPass.Token); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -576,7 +582,7 @@ await Assert.ThrowsAnyAsync(async () => }); var profiler = await TimeMeasure.WithActionAsync((a1, a2, token) => Task.Delay(expected, token), 1, 2, o => o.CancellationToken = ctsShouldPass.Token); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -601,7 +607,7 @@ await Assert.ThrowsAnyAsync(async () => }); var profiler = await TimeMeasure.WithActionAsync((a1, a2, a3, token) => Task.Delay(expected, token), 1, 2, 3, o => o.CancellationToken = ctsShouldPass.Token); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -627,7 +633,7 @@ await Assert.ThrowsAnyAsync(async () => }); var profiler = await TimeMeasure.WithActionAsync((a1, a2, a3, a4, token) => Task.Delay(expected, token), 1, 2, 3, 4, o => o.CancellationToken = ctsShouldPass.Token); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -654,7 +660,7 @@ await Assert.ThrowsAnyAsync(async () => }); var profiler = await TimeMeasure.WithActionAsync((a1, a2, a3, a4, a5, token) => Task.Delay(expected, token), 1, 2, 3, 4, 5, o => o.CancellationToken = ctsShouldPass.Token); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -682,7 +688,7 @@ await Assert.ThrowsAnyAsync(async () => }); var profiler = await TimeMeasure.WithActionAsync((a1, a2, a3, a4, a5, a6, token) => Task.Delay(expected, token), 1, 2, 3, 4, 5, 6, o => o.CancellationToken = ctsShouldPass.Token); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -711,7 +717,7 @@ await Assert.ThrowsAnyAsync(async () => }); var profiler = await TimeMeasure.WithActionAsync((a1, a2, a3, a4, a5, a6, a7, token) => Task.Delay(expected, token), 1, 2, 3, 4, 5, 6, 7, o => o.CancellationToken = ctsShouldPass.Token); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -741,7 +747,7 @@ await Assert.ThrowsAnyAsync(async () => }); var profiler = await TimeMeasure.WithActionAsync((a1, a2, a3, a4, a5, a6, a7, a8, token) => Task.Delay(expected, token), 1, 2, 3, 4, 5, 6, 7, 8, o => o.CancellationToken = ctsShouldPass.Token); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -772,7 +778,7 @@ await Assert.ThrowsAnyAsync(async () => }); var profiler = await TimeMeasure.WithActionAsync((a1, a2, a3, a4, a5, a6, a7, a8, a9, token) => Task.Delay(expected, token), 1, 2, 3, 4, 5, 6, 7, 8, 9, o => o.CancellationToken = ctsShouldPass.Token); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -804,7 +810,7 @@ await Assert.ThrowsAnyAsync(async () => }); var profiler = await TimeMeasure.WithActionAsync((a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, token) => Task.Delay(expected, token), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, o => o.CancellationToken = ctsShouldPass.Token); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -847,7 +853,7 @@ await TimeMeasure.WithFuncAsync(async token => }, o => o.CancellationToken = ctsShouldPass.Token); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.Empty(profiler.Data); @@ -879,7 +885,7 @@ await TimeMeasure.WithFuncAsync(async (a, token) => }, 1, o => o.CancellationToken = ctsShouldPass.Token); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -913,7 +919,7 @@ await TimeMeasure.WithFuncAsync(async (a1, a2, token) => }, 1, 2, o => o.CancellationToken = ctsShouldPass.Token); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -948,7 +954,7 @@ await TimeMeasure.WithFuncAsync(async (a1, a2, a3, token) => }, 1, 2, 3, o => o.CancellationToken = ctsShouldPass.Token); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -984,7 +990,7 @@ await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, token) => }, 1, 2, 3, 4, o => o.CancellationToken = ctsShouldPass.Token); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -1021,7 +1027,7 @@ await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, a5, token) => }, 1, 2, 3, 4, 5, o => o.CancellationToken = ctsShouldPass.Token); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -1059,7 +1065,7 @@ await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, a5, a6, token) => }, 1, 2, 3, 4, 5, 6, o => o.CancellationToken = ctsShouldPass.Token); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -1098,7 +1104,7 @@ await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, a5, a6, a7, token) => }, 1, 2, 3, 4, 5, 6, 7, o => o.CancellationToken = ctsShouldPass.Token); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -1138,7 +1144,7 @@ await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, a5, a6, a7, a8, token) => }, 1, 2, 3, 4, 5, 6, 7, 8, o => o.CancellationToken = ctsShouldPass.Token); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -1179,7 +1185,7 @@ await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, a5, a6, a7, a8, a9, token }, 1, 2, 3, 4, 5, 6, 7, 8, 9, o => o.CancellationToken = ctsShouldPass.Token); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); @@ -1221,7 +1227,7 @@ await TimeMeasure.WithFuncAsync(async (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, }, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, o => o.CancellationToken = ctsShouldPass.Token); Assert.Equal(42, profiler.Result); - Assert.InRange(profiler.Elapsed, expected.Subtract(Jitter), expected.Add(Jitter)); + AssertElapsedAround(profiler.Elapsed, expected); Assert.True(profiler.Member.HasParameters()); Assert.Contains(profiler.Member.Parameters, item => item.ParameterName == "token" && item.ParameterType == typeof(CancellationToken)); Assert.NotEmpty(profiler.Data); From 94a6a57f4421cb27360c8ca3d78bda40daf2d936 Mon Sep 17 00:00:00 2001 From: "aicia[bot]" Date: Thu, 4 Jun 2026 17:15:47 +0200 Subject: [PATCH 4/5] =?UTF-8?q?=E2=9C=85=20tighten=20test=20assertions=20f?= =?UTF-8?q?or=20timing=20and=20caching=20behavior?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add assertion helper method to verify cache entries remain physically present before cleanup. Reduce jitter tolerance in TimeMeasure test to make timing assertions more deterministic. --- test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs | 2 +- .../SlimMemoryCacheTest.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs b/test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs index d6e04aa7..0ab17e83 100644 --- a/test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs +++ b/test/Cuemon.Diagnostics.Tests/TimeMeasureTest.cs @@ -12,7 +12,7 @@ public class TimeMeasureTest : Test { private static readonly TimeSpan ExpectedExecutionTime = TimeSpan.FromSeconds(1); private static readonly TimeSpan LowerJitter = TimeSpan.FromMilliseconds(250); - private static readonly TimeSpan UpperJitter = TimeSpan.FromSeconds(2); + private static readonly TimeSpan UpperJitter = TimeSpan.FromMilliseconds(500); public TimeMeasureTest(ITestOutputHelper output) : base(output) { diff --git a/test/Cuemon.Runtime.Caching.Tests/SlimMemoryCacheTest.cs b/test/Cuemon.Runtime.Caching.Tests/SlimMemoryCacheTest.cs index d0bb57e1..af586734 100644 --- a/test/Cuemon.Runtime.Caching.Tests/SlimMemoryCacheTest.cs +++ b/test/Cuemon.Runtime.Caching.Tests/SlimMemoryCacheTest.cs @@ -235,6 +235,10 @@ public void Add_VerifyBothLogicalAndActualCacheRemovalUponExpirationForThirtySec AssertNamespaceIsLogicallyExpired(Sliding30Namespace); AssertNamespaceIsLogicallyExpired(Absolute30Namespace); + AssertNamespaceIsPhysicallyPresent(Dependency30Namespace); + AssertNamespaceIsPhysicallyPresent(Sliding30Namespace); + AssertNamespaceIsPhysicallyPresent(Absolute30Namespace); + AssertNamespaceIsPhysicallyRemoved(Dependency30Namespace); AssertNamespaceIsPhysicallyRemoved(Sliding30Namespace); AssertNamespaceIsPhysicallyRemoved(Absolute30Namespace); @@ -249,6 +253,10 @@ public void Add_VerifyBothLogicalAndActualCacheRemovalUponExpirationForSixtySeco AssertNamespaceIsLogicallyExpired(Sliding60Namespace); AssertNamespaceIsLogicallyExpired(Absolute60Namespace); + AssertNamespaceIsPhysicallyPresent(Dependency60Namespace); + AssertNamespaceIsPhysicallyPresent(Sliding60Namespace); + AssertNamespaceIsPhysicallyPresent(Absolute60Namespace); + AssertNamespaceIsPhysicallyRemoved(Dependency60Namespace); AssertNamespaceIsPhysicallyRemoved(Sliding60Namespace); AssertNamespaceIsPhysicallyRemoved(Absolute60Namespace); @@ -325,6 +333,14 @@ private void AssertNamespaceIsPhysicallyRemoved(string ns) $"Cache entries in namespace '{ns}' were not physically removed within {CleanupTimeout}."); } + private void AssertNamespaceIsPhysicallyPresent(string ns) + { + var physicalCount = _cache.Where(pair => pair.Value.Namespace == ns).Count(); + + Assert.True(physicalCount == NumberOfItemsToCache, + $"Cache entries in namespace '{ns}' should remain physically present until cleanup. Expected {NumberOfItemsToCache}, actual {physicalCount}."); + } + private void AssertNamespaceIsLogicallyExpired(string ns) { Assert.True(SpinWait.SpinUntil(() => _cache.Count(ns) == 0, CleanupTimeout), From b78f625896ca37aba625c173a0f5c4f5af7fdb4e Mon Sep 17 00:00:00 2001 From: gimlichael Date: Thu, 4 Jun 2026 18:15:19 +0200 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=94=84=20replace=20slimhttpclientfact?= =?UTF-8?q?ory=20with=20statuscodehttpclientfactory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Http/UriExtensionsTest.cs | 62 ++++++++++++++++--- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/test/Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs b/test/Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs index 2e433333..5bf1ab47 100644 --- a/test/Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs +++ b/test/Cuemon.Extensions.Net.Tests/Http/UriExtensionsTest.cs @@ -13,18 +13,14 @@ public class UriExtensionsTest : Test { public UriExtensionsTest(ITestOutputHelper output) : base(output) { - UriExtensions.DefaultHttpClientFactory = new SlimHttpClientFactory(() => new HttpClientHandler() - { - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, - MaxAutomaticRedirections = 10 - }, o => o.HandlerLifetime = TimeSpan.MinValue); } [Fact] public async Task HttpGetAsync_ShouldGetResponseFromUri() { - // Test SlimHttpClientFactory robustness under parallel load using a reliable external server - var uri = new Uri("https://free.mockerapi.com/200"); + var factory = new StatusCodeHttpClientFactory(HttpStatusCode.OK); + UriExtensions.DefaultHttpClientFactory = factory; + var uri = new Uri("https://example.com/200"); var expected = 125; var atomicCount = 0; @@ -38,13 +34,15 @@ await ParallelFactory.ForAsync(0, expected, async (i, ct) => }); Assert.Equal(expected, atomicCount); + Assert.Equal(expected, factory.RequestCount); } [Fact] public async Task HttpGetAsync_ShouldHandleHttpStatusCodes() { - // Test that the extension method properly returns non-OK status codes under parallel load - var uri = new Uri("https://free.mockerapi.com/404"); + var factory = new StatusCodeHttpClientFactory(HttpStatusCode.NotFound); + UriExtensions.DefaultHttpClientFactory = factory; + var uri = new Uri("https://example.com/404"); var expected = 50; var atomicCount = 0; @@ -58,6 +56,52 @@ await ParallelFactory.ForAsync(0, expected, async (i, ct) => }); Assert.Equal(expected, atomicCount); + Assert.Equal(expected, factory.RequestCount); + } + + private sealed class StatusCodeHttpClientFactory : IHttpClientFactory + { + private int _requestCount; + private readonly HttpStatusCode _statusCode; + + public StatusCodeHttpClientFactory(HttpStatusCode statusCode) + { + _statusCode = statusCode; + } + + public int RequestCount => _requestCount; + + public HttpClient CreateClient(string name) + { + return new HttpClient(new StatusCodeHttpMessageHandler(this, _statusCode)); + } + + private void IncrementRequestCount() + { + Interlocked.Increment(ref _requestCount); + } + + private sealed class StatusCodeHttpMessageHandler : HttpMessageHandler + { + private readonly StatusCodeHttpClientFactory _factory; + private readonly HttpStatusCode _statusCode; + + public StatusCodeHttpMessageHandler(StatusCodeHttpClientFactory factory, HttpStatusCode statusCode) + { + _factory = factory; + _statusCode = statusCode; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + _factory.IncrementRequestCount(); + return Task.FromResult(new HttpResponseMessage(_statusCode) + { + Content = new ByteArrayContent(Array.Empty()), + RequestMessage = request + }); + } + } } } }