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

Fix timeout check in WithResult extension #2101

Merged
merged 1 commit into from Jan 17, 2023
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
6 changes: 6 additions & 0 deletions Src/FluentAssertions/AsyncAssertionsExtensions.cs
Expand Up @@ -18,6 +18,12 @@ public static class AsyncAssertionsExtensions
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because"/>.
/// </param>
/// <remarks>
/// Please note that this assertion cannot identify whether the previous assertion was successful or not.
/// In case it was not successful and it is running within an active <see cref="FluentAssertions.Execution.AssertionScope"/>
/// there is no current result to compare with.
/// So, this extension will compare with the default value.
/// </remarks>
public static async Task<AndWhichConstraint<GenericAsyncFunctionAssertions<T>, T>> WithResult<T>(
this Task<AndWhichConstraint<GenericAsyncFunctionAssertions<T>, T>> task,
T expected, string because = "", params object[] becauseArgs)
Expand Down
Expand Up @@ -51,13 +51,13 @@ public GenericAsyncFunctionAssertions(Func<Task<TResult>> subject, IExtractExcep
if (success)
{
bool completesWithinTimeout = await CompletesWithinTimeoutAsync(task, remainingTime);
Execute.Assertion
.ForCondition(completesWithinTimeout)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan);
}
success = Execute.Assertion
.ForCondition(completesWithinTimeout)
.BecauseOf(because, becauseArgs)
.FailWith("Expected {context:task} to complete within {0}{reason}.", timeSpan);
}

TResult result = task.IsCompleted ? task.Result : default;
TResult result = success ? task.Result : default;
return new AndWhichConstraint<GenericAsyncFunctionAssertions<TResult>, TResult>(this, result);
}

Expand Down
11 changes: 10 additions & 1 deletion Tests/FluentAssertions.Specs/FakeClock.cs
Expand Up @@ -9,7 +9,7 @@ namespace FluentAssertions.Specs;
/// Implementation of <see cref="IClock"/> for testing purposes only.
/// </summary>
/// <remarks>
/// It allows you to control the "current" time.
/// It allows you to control the "current" date and time for test purposes.
/// </remarks>
internal class FakeClock : IClock
{
Expand All @@ -25,17 +25,26 @@ Task IClock.DelayAsync(TimeSpan delay, CancellationToken cancellationToken)

public ITimer StartTimer() => new TestTimer(() => elapsedTime);

/// <summary>
/// Advances the internal clock.
/// </summary>
public void Delay(TimeSpan timeToDelay)
{
elapsedTime += timeToDelay;
}

/// <summary>
/// Simulates the completion of the pending delay task.
/// </summary>
public void Complete()
{
// the value is not relevant
delayTask.SetResult(true);
}

/// <summary>
/// Simulates the completion of the pending delay task after the internal clock has been advanced.
/// </summary>
public void CompleteAfter(TimeSpan timeSpan)
{
Delay(timeSpan);
Expand Down
@@ -1,5 +1,4 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using FluentAssertions.Execution;
using FluentAssertions.Extensions;
Expand All @@ -10,6 +9,8 @@
using Xunit;
using Xunit.Sdk;

using static FluentAssertions.FluentActions;

namespace FluentAssertions.Specs.Specialized;

public static class TaskOfTAssertionSpecs
Expand Down Expand Up @@ -165,6 +166,28 @@ public async Task When_task_completes_late_it_in_assertion_scope_should_fail()
await action.Should().ThrowAsync<XunitException>();
}

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

// Act
Func<Task> action = () =>
{
Func<Task<int>> func = () => taskFactory.Task;
using var _ = new AssertionScope();
return func.Should(timer).CompleteWithinAsync(100.Milliseconds()).WithResult(2);
};
timer.Complete();

// Assert
var assertionTask = action.Should().ThrowAsync<XunitException>()
.WithMessage("Expected*to complete within 100ms.*Expected return*to be 2, but found 0.");
await Awaiting(() => assertionTask).Should().CompleteWithinAsync(200.Seconds());
}

[Fact]
public async Task When_task_consumes_time_in_sync_portion_it_should_fail()
{
Expand Down
1 change: 1 addition & 0 deletions docs/_pages/releases.md
Expand Up @@ -12,6 +12,7 @@ sidebar:
### What's new

### Fixes
* Fixed hanging of `CompleteWithinAsync` when used with `WithResult` and `AssertionScope` - [#2101](https://github.com/fluentassertions/fluentassertions/pull/2101)

* `BeEquivalentTo` no longer crashes on fields hiding base-class fields - [#1990](https://github.com/fluentassertions/fluentassertions/pull/1990)

Expand Down