Skip to content

Commit

Permalink
Merge pull request #1762 from jnyrup/issue1761
Browse files Browse the repository at this point in the history
Take sync work into account in CompleteWithinAsync
  • Loading branch information
jnyrup committed Dec 31, 2021
2 parents 3e39b96 + 20dc1d2 commit 587f5f1
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 24 deletions.
32 changes: 21 additions & 11 deletions Src/FluentAssertions/Specialized/AsyncFunctionAssertions.cs
Expand Up @@ -46,22 +46,32 @@ public AsyncFunctionAssertions(Func<TTask> subject, IExtractExceptions extractor
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:task} to complete within {0}{reason}, but found <null>.", timeSpan);

using var timeoutCancellationTokenSource = new CancellationTokenSource();
ITimer timer = Clock.StartTimer();
TTask task = Subject.Invoke();
TimeSpan remainingTime = timeSpan - timer.Elapsed;

Task completedTask =
await Task.WhenAny(task, Clock.DelayAsync(timeSpan, timeoutCancellationTokenSource.Token));
bool success = Execute.Assertion
.ForCondition(remainingTime >= TimeSpan.Zero)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan);

if (completedTask == task)
if (success)
{
timeoutCancellationTokenSource.Cancel();
await completedTask;
}
using var timeoutCancellationTokenSource = new CancellationTokenSource();
Task completedTask =
await Task.WhenAny(task, Clock.DelayAsync(remainingTime, timeoutCancellationTokenSource.Token));

Execute.Assertion
.ForCondition(completedTask == task)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan);
if (completedTask == task)
{
timeoutCancellationTokenSource.Cancel();
await completedTask;
}

Execute.Assertion
.ForCondition(completedTask == task)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan);
}

return new AndConstraint<TAssertions>((TAssertions)this);
}
Expand Down
Expand Up @@ -4,7 +4,7 @@

namespace FluentAssertions.Specialized
{
internal class FunctionAssertionHelpers
internal static class FunctionAssertionHelpers
{
internal static T NotThrow<T>(Func<T> subject, string because, object[] becauseArgs)
{
Expand Down
32 changes: 21 additions & 11 deletions Src/FluentAssertions/Specialized/GenericAsyncFunctionAssertions.cs
Expand Up @@ -37,22 +37,32 @@ public GenericAsyncFunctionAssertions(Func<Task<TResult>> subject, IExtractExcep
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context} to complete within {0}{reason}, but found <null>.", timeSpan);

using var timeoutCancellationTokenSource = new CancellationTokenSource();
ITimer timer = Clock.StartTimer();
Task<TResult> task = Subject.Invoke();
TimeSpan remainingTime = timeSpan - timer.Elapsed;

Task completedTask =
await Task.WhenAny(task, Clock.DelayAsync(timeSpan, timeoutCancellationTokenSource.Token));
bool success = Execute.Assertion
.ForCondition(remainingTime >= TimeSpan.Zero)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan);

if (completedTask == task)
if (success)
{
timeoutCancellationTokenSource.Cancel();
await completedTask;
}
using var timeoutCancellationTokenSource = new CancellationTokenSource();
Task completedTask =
await Task.WhenAny(task, Clock.DelayAsync(remainingTime, timeoutCancellationTokenSource.Token));

Execute.Assertion
.ForCondition(completedTask == task)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan);
if (completedTask == task)
{
timeoutCancellationTokenSource.Cancel();
await completedTask;
}

Execute.Assertion
.ForCondition(completedTask == task)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan);
}

return new AndWhichConstraint<GenericAsyncFunctionAssertions<TResult>, TResult>(this, task.Result);
}
Expand Down
26 changes: 25 additions & 1 deletion Tests/FluentAssertions.Specs/Specialized/TaskAssertionSpecs.cs
@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions.Common;
using FluentAssertions.Extensions;
using Xunit;
using Xunit.Sdk;
Expand All @@ -26,7 +27,6 @@ public void When_getting_the_subject_it_should_remain_unchanged()
public async Task When_subject_is_null_when_expecting_to_complete_async_it_should_throw()
{
// Arrange
var timer = new FakeClock();
var timeSpan = 0.Milliseconds();
Func<Task> action = null;

Expand Down Expand Up @@ -55,6 +55,30 @@ public async Task When_task_completes_fast_async_it_should_succeed()
await action.Should().NotThrowAsync();
}

[Fact]
public async Task Sync_work_in_async_method_is_taken_into_account()
{
// Arrange
var timer = new FakeClock();
var taskFactory = new TaskCompletionSource<bool>();

// Act
Func<Task> action = () => taskFactory
.Awaiting(t =>
{
timer.Delay(101.Milliseconds());
return (Task)t.Task;
})
.Should(timer)
.CompleteWithinAsync(100.Milliseconds());

taskFactory.SetResult(true);
timer.Complete();

// Assert
await action.Should().ThrowAsync<XunitException>();
}

[UIFact]
public async Task When_task_completes_on_UI_thread_fast_async_it_should_succeed()
{
Expand Down
26 changes: 26 additions & 0 deletions Tests/FluentAssertions.Specs/Specialized/TaskOfTAssertionSpecs.cs
Expand Up @@ -122,6 +122,32 @@ public async Task When_task_completes_slow_async_it_should_fail()
await action.Should().ThrowAsync<XunitException>();
}

[Fact]
public async Task Sync_work_in_async_method_is_taken_into_account()
{
// Arrange
var timer = new FakeClock();
var taskFactory = new TaskCompletionSource<int>();

// Act
Func<Task> action = () =>
{
Func<Task<int>> func = () =>
{
timer.Delay(101.Milliseconds());
return taskFactory.Task;
};
return func.Should(timer).CompleteWithinAsync(100.Milliseconds());
};

taskFactory.SetResult(99);
timer.Complete();

// Assert
await action.Should().ThrowAsync<XunitException>();
}

#endregion

#region NotThrowAfterAsync
Expand Down
2 changes: 2 additions & 0 deletions docs/_pages/releases.md
Expand Up @@ -13,9 +13,11 @@ sidebar:
* Adding `ThatAreAsync()` and `ThatAreNotAsync()` for filtering in method assertions - [#1725](https://github.com/fluentassertions/fluentassertions/pull/1725)
* Adding `ThatAreVirtual()` and `ThatAreNotVirtual()` for filtering in method assertions - [#1744](https://github.com/fluentassertions/fluentassertions/pull/1744)
* Adding collection content to assertion messages for `HaveCountGreaterThan()`, `HaveCountGreaterThanOrEqualTo()`, `HaveCountLessThan()` and `HaveCountLessThanOrEqualTo()` - [#1760](https://github.com/fluentassertions/fluentassertions/pull/1760)

### Fixes
* Prevent multiple enumeration of `IEnumerable`s in parameter-less `ContainSingle()` - [#1753](https://github.com/fluentassertions/fluentassertions/pull/1753)
* Change `HaveCount()` assertion message order to state expected and actual collection count before dumping its content` - [#1760](https://github.com/fluentassertions/fluentassertions/pull/1760)
* `CompleteWithinAsync` did not take initial sync computation into account when measuring execution time - [1762](https://github.com/fluentassertions/fluentassertions/pull/1762).

## 6.2.0

Expand Down

0 comments on commit 587f5f1

Please sign in to comment.