/// <summary> /// Asserts that the current <see cref="Task{T}"/> will complete within the specified time. /// </summary> /// <param name="timeSpan">The allowed time span for the operation.</param> /// <param name="because"> /// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion /// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically. /// </param> /// <param name="becauseArgs"> /// Zero or more objects to format using the placeholders in <see cref="because" />. /// </param> public async Task <AndConstraint <AsyncFunctionAssertions> > CompleteWithinAsync( TimeSpan timeSpan, string because = "", params object[] becauseArgs) { Execute.Assertion .ForCondition(!ReferenceEquals(Subject, null)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context:task} to complete within {0}{reason}, but found <null>.", timeSpan); using (var timeoutCancellationTokenSource = new CancellationTokenSource()) { Task task = Subject.ExecuteInDefaultSynchronizationContext(); Task completedTask = await Task.WhenAny(task, clock.DelayAsync(timeSpan, timeoutCancellationTokenSource.Token)); 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 <AsyncFunctionAssertions>(this)); } }
/// <summary> /// Asserts that the current <see cref="Func{T}"/> stops throwing any exception /// after a specified amount of time. /// </summary> /// <remarks> /// The <see cref="Func{T}"/> is invoked. If it raises an exception, /// the invocation is repeated until it either stops raising any exceptions /// or the specified wait time is exceeded. /// </remarks> /// <param name="waitTime"> /// The time after which the <see cref="Func{T}"/> should have stopped throwing any exception. /// </param> /// <param name="pollInterval"> /// The time between subsequent invocations of the <see cref="Func{T}"/>. /// </param> /// <param name="because"> /// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion /// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically. /// </param> /// <param name="becauseArgs"> /// Zero or more objects to format using the placeholders in <see cref="because" />. /// </param> /// <exception cref="ArgumentOutOfRangeException">Throws if waitTime or pollInterval are negative.</exception> public Task NotThrowAfterAsync(TimeSpan waitTime, TimeSpan pollInterval, string because = "", params object[] becauseArgs) { if (waitTime < TimeSpan.Zero) { throw new ArgumentOutOfRangeException(nameof(waitTime), $"The value of {nameof(waitTime)} must be non-negative."); } if (pollInterval < TimeSpan.Zero) { throw new ArgumentOutOfRangeException(nameof(pollInterval), $"The value of {nameof(pollInterval)} must be non-negative."); } Execute.Assertion .ForCondition(!ReferenceEquals(Subject, null)) .BecauseOf(because, becauseArgs) .FailWith("Expected {context} not to throw any exceptions after {0}{reason}, but found <null>.", waitTime); Func <Task> wrappedSubject = Subject.ExecuteInDefaultSynchronizationContext; return(assertionTask()); async Task assertionTask() { TimeSpan? invocationEndTime = null; Exception exception = null; ITimer timer = clock.StartTimer(); while (invocationEndTime is null || invocationEndTime < waitTime) { exception = await InvokeWithInterceptionAsync(wrappedSubject); if (exception is null) { return; } await clock.DelayAsync(pollInterval, CancellationToken.None); invocationEndTime = timer.Elapsed; } Execute.Assertion .BecauseOf(because, becauseArgs) .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception); } }
/// <summary> /// Asserts that the current <see cref="Func{T}"/> stops throwing any exception /// after a specified amount of time. /// </summary> /// <remarks> /// The <see cref="Func{T}"/> is invoked. If it raises an exception, /// the invocation is repeated until it either stops raising any exceptions /// or the specified wait time is exceeded. /// </remarks> /// <param name="waitTime"> /// The time after which the <see cref="Func{T}"/> should have stopped throwing any exception. /// </param> /// <param name="pollInterval"> /// The time between subsequent invocations of the <see cref="Func{T}"/>. /// </param> /// <param name="because"> /// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion /// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically. /// </param> /// <param name="becauseArgs"> /// Zero or more objects to format using the placeholders in <see cref="because" />. /// </param> /// <exception cref="ArgumentOutOfRangeException">Throws if waitTime or pollInterval are negative.</exception> public Task NotThrowAfterAsync(TimeSpan waitTime, TimeSpan pollInterval, string because = "", params object[] becauseArgs) { if (waitTime < TimeSpan.Zero) { throw new ArgumentOutOfRangeException(nameof(waitTime), $"The value of {nameof(waitTime)} must be non-negative."); } if (pollInterval < TimeSpan.Zero) { throw new ArgumentOutOfRangeException(nameof(pollInterval), $"The value of {nameof(pollInterval)} must be non-negative."); } return(assertionTask()); async Task assertionTask() { TimeSpan? invocationEndTime = null; Exception exception = null; var timer = clock.StartTimer(); while (invocationEndTime is null || invocationEndTime < waitTime) { exception = await InvokeSubjectWithInterceptionAsync(); if (exception is null) { return; } await clock.DelayAsync(pollInterval, CancellationToken.None); invocationEndTime = timer.Elapsed; } Execute.Assertion .BecauseOf(because, becauseArgs) .FailWith("Did not expect any exceptions after {0}{reason}, but found {1}.", waitTime, exception); } }
/// <summary> /// Asserts that the current <see cref="Task{T}"/> will complete within the specified time. /// </summary> /// <param name="timeSpan">The allowed time span for the operation.</param> /// <param name="because"> /// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion /// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically. /// </param> /// <param name="becauseArgs"> /// Zero or more objects to format using the placeholders in <see cref="because" />. /// </param> public async Task <AndWhichConstraint <GenericAsyncFunctionAssertions <TResult>, TResult> > CompleteWithinAsync( TimeSpan timeSpan, string because = "", params object[] becauseArgs) { using (var timeoutCancellationTokenSource = new CancellationTokenSource()) { Task <TResult> task = subject(); Task completedTask = await Task.WhenAny(task, clock.DelayAsync(timeSpan, timeoutCancellationTokenSource.Token)); 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)); } }