Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Take sync work into account in CompleteWithinAsync #1762

Merged
merged 1 commit into from Dec 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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