From 9050f7d83a67cde8279ff21305b4b92a31f6657f Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Fri, 4 Mar 2022 03:56:00 +0700 Subject: [PATCH 1/3] Change Within methods to async --- src/core/Akka.TestKit/TestKitBase_Within.cs | 181 +++++++++++++------- 1 file changed, 120 insertions(+), 61 deletions(-) diff --git a/src/core/Akka.TestKit/TestKitBase_Within.cs b/src/core/Akka.TestKit/TestKitBase_Within.cs index 9475108b242..67194b32c6a 100644 --- a/src/core/Akka.TestKit/TestKitBase_Within.cs +++ b/src/core/Akka.TestKit/TestKitBase_Within.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Akka.TestKit.Internal; +using Nito.AsyncEx.Synchronous; namespace Akka.TestKit { @@ -27,17 +28,33 @@ public abstract partial class TestKitBase /// TBD /// TBD /// TBD - public void Within(TimeSpan max, Action action, TimeSpan? epsilonValue = null) + /// + public void Within( + TimeSpan max, + Action action, + TimeSpan? epsilonValue = null, + CancellationToken cancellationToken = default) { - Within(TimeSpan.Zero, max, action, epsilonValue: epsilonValue); + WithinAsync(max, async () => action, epsilonValue, cancellationToken) + .WaitAndUnwrapException(); } /// /// Async version of Within /// - public Task WithinAsync(TimeSpan max, Func actionAsync, TimeSpan? epsilonValue = null) + public async Task WithinAsync( + TimeSpan max, + Func actionAsync, + TimeSpan? epsilonValue = null, + CancellationToken cancellationToken = default) { - return WithinAsync(TimeSpan.Zero, max, actionAsync, epsilonValue: epsilonValue); + await WithinAsync( + min: TimeSpan.Zero, + max: max, + actionAsync: actionAsync, + epsilonValue: epsilonValue, + cancellationToken: cancellationToken) + .ConfigureAwait(false); } /// @@ -52,17 +69,42 @@ public Task WithinAsync(TimeSpan max, Func actionAsync, TimeSpan? epsilonV /// TBD /// TBD /// TBD - public void Within(TimeSpan min, TimeSpan max, Action action, string hint = null, TimeSpan? epsilonValue = null) + /// + public void Within( + TimeSpan min, + TimeSpan max, + Action action, + string hint = null, + TimeSpan? epsilonValue = null, + CancellationToken cancellationToken = default) { - Within(min, max, () => { action(); return null; }, hint, epsilonValue); + WithinAsync(min, max, async () => action, hint, epsilonValue, cancellationToken) + .WaitAndUnwrapException(); } /// /// Async version of /// - public Task WithinAsync(TimeSpan min, TimeSpan max, Func actionAsync, string hint = null, TimeSpan? epsilonValue = null) + public async Task WithinAsync( + TimeSpan min, + TimeSpan max, + Func actionAsync, + string hint = null, + TimeSpan? epsilonValue = null, + CancellationToken cancellationToken = default) { - return WithinAsync(min, max, async () => { await actionAsync(); return null; }, hint, epsilonValue); + await WithinAsync( + min: min, + max: max, + function: async () => + { + await actionAsync(); + return null; + }, + hint: hint, + epsilonValue: epsilonValue, + cancellationToken: cancellationToken) + .ConfigureAwait(false); } /// @@ -76,12 +118,23 @@ public Task WithinAsync(TimeSpan min, TimeSpan max, Func actionAsync, stri /// TBD /// TBD /// TBD + /// /// TBD - public T Within(TimeSpan max, Func function, TimeSpan? epsilonValue = null) + public T Within( + TimeSpan max, + Func function, + TimeSpan? epsilonValue = null, + CancellationToken cancellationToken = default) { - return Within(TimeSpan.Zero, max, function, epsilonValue: epsilonValue); + return WithinAsync( + min: TimeSpan.Zero, + max: max, + function: () => Task.FromResult(function()), + epsilonValue: epsilonValue, + cancellationToken: cancellationToken) + .WaitAndUnwrapException(); } - + /// /// Execute code block while bounding its execution time between 0 seconds and . /// `within` blocks may be nested. All methods in this class which take maximum wait times @@ -93,10 +146,21 @@ public T Within(TimeSpan max, Func function, TimeSpan? epsilonValue = null /// TBD /// TBD /// TBD + /// /// TBD - public Task WithinAsync(TimeSpan max, Func> function, TimeSpan? epsilonValue = null) + public async Task WithinAsync( + TimeSpan max, + Func> function, + TimeSpan? epsilonValue = null, + CancellationToken cancellationToken = default) { - return WithinAsync(TimeSpan.Zero, max, function, epsilonValue: epsilonValue); + return await WithinAsync( + min: TimeSpan.Zero, + max: max, + function: function, + epsilonValue: epsilonValue, + cancellationToken: cancellationToken) + .ConfigureAwait(false); } /// @@ -112,53 +176,24 @@ public Task WithinAsync(TimeSpan max, Func> function, TimeSpan? ep /// TBD /// TBD /// TBD + /// /// TBD - public T Within(TimeSpan min, TimeSpan max, Func function, string hint = null, TimeSpan? epsilonValue = null) + public T Within( + TimeSpan min, + TimeSpan max, + Func function, + string hint = null, + TimeSpan? epsilonValue = null, + CancellationToken cancellationToken = default) { - min.EnsureIsPositiveFinite("min"); - min.EnsureIsPositiveFinite("max"); - max = Dilated(max); - var start = Now; - var rem = _testState.End.HasValue ? _testState.End.Value - start : Timeout.InfiniteTimeSpan; - _assertions.AssertTrue(rem.IsInfiniteTimeout() || rem >= min, "Required min time {0} not possible, only {1} left. {2}", min, rem, hint ?? ""); - - _testState.LastWasNoMsg = false; - - var maxDiff = max.Min(rem); - var prevEnd = _testState.End; - _testState.End = start + maxDiff; - - T ret; - try - { - ret = function(); - } - finally - { - _testState.End = prevEnd; - } - - var elapsed = Now - start; - var wasTooFast = elapsed < min; - if(wasTooFast) - { - const string failMessage = "Failed: Block took {0}, should have at least been {1}. {2}"; - ConditionalLog(failMessage, elapsed, min, hint ?? ""); - _assertions.Fail(failMessage, elapsed, min, hint ?? ""); - } - if (!_testState.LastWasNoMsg) - { - epsilonValue = epsilonValue ?? TimeSpan.Zero; - var tookTooLong = elapsed > maxDiff + epsilonValue; - if(tookTooLong) - { - const string failMessage = "Failed: Block took {0}, exceeding {1}. {2}"; - ConditionalLog(failMessage, elapsed, maxDiff, hint ?? ""); - _assertions.Fail(failMessage, elapsed, maxDiff, hint ?? ""); - } - } - - return ret; + return WithinAsync( + min: min, + max: max, + function: () => Task.FromResult(function()), + hint: hint, + epsilonValue: epsilonValue, + cancellationToken: cancellationToken) + .WaitAndUnwrapException(); } /// @@ -174,8 +209,15 @@ public T Within(TimeSpan min, TimeSpan max, Func function, string hint = n /// TBD /// TBD /// TBD + /// /// TBD - public async Task WithinAsync(TimeSpan min, TimeSpan max, Func> function, string hint = null, TimeSpan? epsilonValue = null) + public async Task WithinAsync( + TimeSpan min, + TimeSpan max, + Func> function, + string hint = null, + TimeSpan? epsilonValue = null, + CancellationToken cancellationToken = default) { min.EnsureIsPositiveFinite("min"); min.EnsureIsPositiveFinite("max"); @@ -193,7 +235,23 @@ public async Task WithinAsync(TimeSpan min, TimeSpan max, Func> fu T ret; try { - ret = await function(); + var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + using (cts) + { + var funcTask = function(); + var timeoutTask = Task.Delay(maxDiff + TimeSpan.FromSeconds(1), cts.Token); + + var finishedTask = await Task.WhenAny(funcTask, timeoutTask); + if (finishedTask == funcTask) + { + ret = await function(); + cts.Cancel(); + } + else + { + ret = default; + } + } } finally { @@ -208,9 +266,10 @@ public async Task WithinAsync(TimeSpan min, TimeSpan max, Func> fu ConditionalLog(failMessage, elapsed, min, hint ?? ""); _assertions.Fail(failMessage, elapsed, min, hint ?? ""); } + if (!_testState.LastWasNoMsg) { - epsilonValue = epsilonValue ?? TimeSpan.Zero; + epsilonValue ??= TimeSpan.Zero; var tookTooLong = elapsed > maxDiff + epsilonValue; if(tookTooLong) { From 4a5dd64413e3817f4617591701c955ed21b3ad02 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Fri, 4 Mar 2022 22:16:46 +0700 Subject: [PATCH 2/3] Fix async Within --- .../TestKitBaseTests/WithinTests.cs | 46 +++++++ .../Akka.TestKit/TestKitBase_AwaitAssert.cs | 5 +- src/core/Akka.TestKit/TestKitBase_Within.cs | 125 ++++++++++++++---- 3 files changed, 147 insertions(+), 29 deletions(-) diff --git a/src/core/Akka.TestKit.Tests/TestKitBaseTests/WithinTests.cs b/src/core/Akka.TestKit.Tests/TestKitBaseTests/WithinTests.cs index 2592cf8ad9c..2dc1ceff526 100644 --- a/src/core/Akka.TestKit.Tests/TestKitBaseTests/WithinTests.cs +++ b/src/core/Akka.TestKit.Tests/TestKitBaseTests/WithinTests.cs @@ -6,7 +6,13 @@ //----------------------------------------------------------------------- using System; +using System.Threading.Tasks; +using FluentAssertions; +using FluentAssertions.Execution; +using FluentAssertions.Extensions; using Xunit; +using Xunit.Sdk; +using static FluentAssertions.FluentActions; namespace Akka.TestKit.Tests.TestKitBaseTests { @@ -17,5 +23,45 @@ public void Within_should_increase_max_timeout_by_the_provided_epsilon_value() { Within(TimeSpan.FromSeconds(1), () => ExpectNoMsg(), TimeSpan.FromMilliseconds(50)); } + + [Fact] + public void Within_should_respect_minimum_time() + { + Within(0.3.Seconds(), 1.Seconds(), () => ExpectNoMsg(0.4.Seconds()), "", 0.1.Seconds()); + } + + [Fact] + public async Task WithinAsync_should_respect_minimum_time() + { + await WithinAsync( + 0.3.Seconds(), + 1.Seconds(), + async () => await ExpectNoMsgAsync(0.4.Seconds()), + "", + 0.1.Seconds()); + } + + [Fact] + public void Within_should_throw_if_execution_is_shorter_than_minimum_time() + { + Invoking(() => + { + Within(0.5.Seconds(), 1.Seconds(), () => ExpectNoMsg(0.1.Seconds()), null, 0.1.Seconds()); + }).Should().Throw(); + } + + [Fact] + public async Task WithinAsync_should_throw_if_execution_is_shorter_than_minimum_time() + { + await Awaiting(async () => + { + await WithinAsync( + 0.5.Seconds(), + 1.Seconds(), + async () => await ExpectNoMsgAsync(0.1.Seconds()), + null, + 0.1.Seconds()); + }).Should().ThrowAsync(); + } } } diff --git a/src/core/Akka.TestKit/TestKitBase_AwaitAssert.cs b/src/core/Akka.TestKit/TestKitBase_AwaitAssert.cs index ccf2907add1..18ed904b19d 100644 --- a/src/core/Akka.TestKit/TestKitBase_AwaitAssert.cs +++ b/src/core/Akka.TestKit/TestKitBase_AwaitAssert.cs @@ -35,8 +35,8 @@ public abstract partial class TestKitBase /// public void AwaitAssert(Action assertion, TimeSpan? duration=null, TimeSpan? interval=null, CancellationToken cancellationToken = default) { - var task = AwaitAssertAsync(assertion, duration, interval, cancellationToken); - task.WaitAndUnwrapException(); + AwaitAssertAsync(assertion, duration, interval, cancellationToken) + .WaitAndUnwrapException(); } /// @@ -53,6 +53,7 @@ public async Task AwaitAssertAsync(Action assertion, TimeSpan? duration=null, Ti cancellationToken.ThrowIfCancellationRequested(); try { + // TODO: assertion can run forever, need a way to stop this if this happens. assertion(); return; } diff --git a/src/core/Akka.TestKit/TestKitBase_Within.cs b/src/core/Akka.TestKit/TestKitBase_Within.cs index 67194b32c6a..e0b23b3a2d6 100644 --- a/src/core/Akka.TestKit/TestKitBase_Within.cs +++ b/src/core/Akka.TestKit/TestKitBase_Within.cs @@ -35,8 +35,38 @@ public void Within( TimeSpan? epsilonValue = null, CancellationToken cancellationToken = default) { - WithinAsync(max, async () => action, epsilonValue, cancellationToken) - .WaitAndUnwrapException(); + WithinAsync( + min: TimeSpan.Zero, + max: max, + function: () => + { + action(); + return Task.FromResult((object)null); + }, + hint: null, + epsilonValue: epsilonValue, + cancellationToken: cancellationToken) + .ConfigureAwait(false).GetAwaiter().GetResult(); + } + + public async Task WithinAsync( + TimeSpan max, + Action action, + TimeSpan? epsilonValue = null, + CancellationToken cancellationToken = default) + { + await WithinAsync( + min: TimeSpan.Zero, + max: max, + function: () => + { + action(); + return Task.FromResult((object)null); + }, + hint: null, + epsilonValue: epsilonValue, + cancellationToken: cancellationToken) + .ConfigureAwait(false); } /// @@ -51,7 +81,12 @@ public async Task WithinAsync( await WithinAsync( min: TimeSpan.Zero, max: max, - actionAsync: actionAsync, + function: async () => + { + await actionAsync().ConfigureAwait(false); + return Task.FromResult((object)null); + }, + hint: null, epsilonValue: epsilonValue, cancellationToken: cancellationToken) .ConfigureAwait(false); @@ -78,10 +113,42 @@ public void Within( TimeSpan? epsilonValue = null, CancellationToken cancellationToken = default) { - WithinAsync(min, max, async () => action, hint, epsilonValue, cancellationToken) - .WaitAndUnwrapException(); + WithinAsync( + min: min, + max: max, + function: () => + { + action(); + return Task.FromResult((object)null); + }, + hint: hint, + epsilonValue: epsilonValue, + cancellationToken: cancellationToken) + .ConfigureAwait(false).GetAwaiter().GetResult(); } + public async Task WithinAsync( + TimeSpan min, + TimeSpan max, + Action action, + string hint = null, + TimeSpan? epsilonValue = null, + CancellationToken cancellationToken = default) + { + await WithinAsync( + min: min, + max: max, + function: () => + { + action(); + return Task.FromResult((object)null); + }, + hint: hint, + epsilonValue: epsilonValue, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + /// /// Async version of /// @@ -93,13 +160,13 @@ public async Task WithinAsync( TimeSpan? epsilonValue = null, CancellationToken cancellationToken = default) { - await WithinAsync( + await WithinAsync( min: min, max: max, function: async () => { - await actionAsync(); - return null; + await actionAsync().ConfigureAwait(false); + return (object)null; }, hint: hint, epsilonValue: epsilonValue, @@ -130,9 +197,10 @@ public T Within( min: TimeSpan.Zero, max: max, function: () => Task.FromResult(function()), - epsilonValue: epsilonValue, + hint: null, + epsilonValue: epsilonValue, cancellationToken: cancellationToken) - .WaitAndUnwrapException(); + .ConfigureAwait(false).GetAwaiter().GetResult(); } /// @@ -158,6 +226,7 @@ public async Task WithinAsync( min: TimeSpan.Zero, max: max, function: function, + hint: null, epsilonValue: epsilonValue, cancellationToken: cancellationToken) .ConfigureAwait(false); @@ -196,6 +265,24 @@ public T Within( .WaitAndUnwrapException(); } + public async Task WithinAsync( + TimeSpan min, + TimeSpan max, + Func function, + string hint = null, + TimeSpan? epsilonValue = null, + CancellationToken cancellationToken = default) + { + return await WithinAsync( + min: min, + max: max, + function: () => Task.FromResult(function()), + hint: hint, + epsilonValue: epsilonValue, + cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + /// /// Execute code block while bounding its execution time between and . /// `within` blocks may be nested. All methods in this class which take maximum wait times @@ -235,23 +322,7 @@ public async Task WithinAsync( T ret; try { - var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - using (cts) - { - var funcTask = function(); - var timeoutTask = Task.Delay(maxDiff + TimeSpan.FromSeconds(1), cts.Token); - - var finishedTask = await Task.WhenAny(funcTask, timeoutTask); - if (finishedTask == funcTask) - { - ret = await function(); - cts.Cancel(); - } - else - { - ret = default; - } - } + ret = await function(); } finally { From 0fb2e57c1b7381f92c6b95d325ca6b5fba817498 Mon Sep 17 00:00:00 2001 From: Gregorius Soedharmo Date: Fri, 4 Mar 2022 23:06:08 +0700 Subject: [PATCH 3/3] Fix documentation --- src/core/Akka.TestKit/TestKitBase.cs | 4 ++-- src/core/Akka.TestKit/TestKitBase_Within.cs | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/core/Akka.TestKit/TestKitBase.cs b/src/core/Akka.TestKit/TestKitBase.cs index 0d0cd6f12fd..d4e2c4f8746 100644 --- a/src/core/Akka.TestKit/TestKitBase.cs +++ b/src/core/Akka.TestKit/TestKitBase.cs @@ -364,7 +364,7 @@ public void SetAutoPilot(AutoPilot pilot) /// /// /// Retrieves the time remaining for execution of the innermost enclosing - /// Within block. + /// Within block. /// If missing that, then it returns the properly dilated default for this /// case from settings (key: "akka.test.single-expect-default"). /// @@ -378,7 +378,7 @@ public TimeSpan RemainingOrDefault /// /// /// Retrieves the time remaining for execution of the innermost enclosing - /// Within block. + /// Within block. /// /// The returned value is always finite. /// diff --git a/src/core/Akka.TestKit/TestKitBase_Within.cs b/src/core/Akka.TestKit/TestKitBase_Within.cs index e0b23b3a2d6..8bb348cffa1 100644 --- a/src/core/Akka.TestKit/TestKitBase_Within.cs +++ b/src/core/Akka.TestKit/TestKitBase_Within.cs @@ -49,6 +49,9 @@ public void Within( .ConfigureAwait(false).GetAwaiter().GetResult(); } + /// + /// Async version of + /// public async Task WithinAsync( TimeSpan max, Action action, @@ -70,7 +73,8 @@ await WithinAsync( } /// - /// Async version of Within + /// Async version of + /// that takes a instead of an /// public async Task WithinAsync( TimeSpan max, @@ -127,6 +131,9 @@ public void Within( .ConfigureAwait(false).GetAwaiter().GetResult(); } + /// + /// Async version of + /// public async Task WithinAsync( TimeSpan min, TimeSpan max, @@ -150,7 +157,8 @@ await WithinAsync( } /// - /// Async version of + /// Async version of + /// that takes a instead of an /// public async Task WithinAsync( TimeSpan min, @@ -265,6 +273,9 @@ public T Within( .WaitAndUnwrapException(); } + /// + /// Async version of + /// public async Task WithinAsync( TimeSpan min, TimeSpan max,