-
-
Notifications
You must be signed in to change notification settings - Fork 540
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
ExecutionTimeAssertions works on cases where the code deadlocks, test… #842
Conversation
…s finish as soon as conditions are met or failed instead of running indefinitely.
Looks like I have more work to do... |
I like the idea to handle deadlocks and return early, when the criteria has been meet. How about this approach: internal Task Task { get; }
protected ExecutionTime(Action action, string actionDescription)
{
ActionDescription = actionDescription;
Task = Task.Run(action);
} Add the following method to internal TimeSpan Execute(TimeSpan timeout)
{
var stopwatch = Stopwatch.StartNew();
Task.Wait(timeout);
stopwatch.Stop();
return stopwatch.Elapsed;
} Change public ExecutionTimeAssertions(ExecutionTime executionTime)
{
this.executionTime = executionTime;
} In the assertion method like e.g. public void BeLessOrEqualTo(TimeSpan maxDuration, string because = "", params object[] becauseArgs)
{
TimeSpan duration = executionTime.Execute(maxDuration);
Execute.Assertion
.ForCondition(duration.CompareTo(maxDuration) <= 0)
...
} |
It seems to me you are assuming that there is only one condition can be applied to the ExectionTime. While that might be the case I can see that API is meant for multiple possible assertions on the same exection time, and I don't want to rerun the task for each of them, especially if it would end up blocking a thread or something. |
I think I can make this work with Task I like most of your suggestion but assertion and execution are separate concerns, I don't want to specialize the execution for each type of assertion, it would unnecessarily complicate the code. And we don't just know for how long we need to run for |
Ended up spending more time on this than expected, but build succeeded, verify the PR again and let me know if I should make other changes. |
//----------------------------------------------------------------------------------------------------------- | ||
// Assert | ||
//----------------------------------------------------------------------------------------------------------- | ||
act.Should().Throw<XunitException>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't you at least verify part of the message. Because if your code doesn't work, you'll still get an XUnit exception if I'm not mistaken. Same for the other tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, if my code doesnt work, the test never finishes to run, but fair enough, better to be safe
{ | ||
break; | ||
} | ||
Task.Delay(rate).Wait(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is safe in all environments. I've seen lockups on AppVeyor while waiting for Wait
call. I would just use a Thread.Sleep
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, wanted to use that, originally and yes, .Result blocks a thread here, ill see if i can improve
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread.Sleep()
is available in net45+ and netstandard2.
For netstandard1.3 and netstandard1.6 there is System.Threading.Thread
.
So I think there is an even more elegant solution, the execution is already wrapped inside a Task.Run(), I just wait on it to finish with the polling rate as the timeout. If execution finishes earlier than that then the test finishes earlier as well, if not then the assertion passes or fail without needing for the execution to actually finish. |
|
||
internal Exception Exception { get; private set; } | ||
|
||
internal readonly Stopwatch Stopwatch; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this should be private readonly Stopwatch stopwatch;
so private
and lowercase s
.
{ | ||
break; | ||
} | ||
Task.Delay(rate).Wait(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thread.Sleep()
is available in net45+ and netstandard2.
For netstandard1.3 and netstandard1.6 there is System.Threading.Thread
.
/// <param name="condition">Condition to check un the current elapsed time.</param> | ||
/// <param name="result">The expected result when polling is stopped.</param> | ||
/// <param name="stopExecution">If polling is stopped then task execution is also stopped.</param> | ||
/// <param name="rate">The rate at which the conition is re-checked.</param> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
conition = condition
/// Checks the executing action if it satisfies a condition. | ||
/// If the execution runs into an exeption, then this will rethrow it. | ||
/// </summary> | ||
/// <param name="condition">Condition to check un the current elapsed time.</param> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
un = on
/// </summary> | ||
/// <param name="condition">Condition to check un the current elapsed time.</param> | ||
/// <param name="result">The expected result when polling is stopped.</param> | ||
/// <param name="stopExecution">If polling is stopped then task execution is also stopped.</param> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stopExecution doesn't exists anymore?
/// <param name="rate">The rate at which the conition is re-checked.</param> | ||
private void PollUntil(Func<TimeSpan, bool> condition, bool result, TimeSpan rate) | ||
{ | ||
var forcedStop = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is forcedStop
ever set to true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remnants from a feature that's not possible, I originally planned to use thread and kill it if not needed anymore :)
{ | ||
break; | ||
} | ||
execution.Task.Wait(rate); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the return value of Wait
is intentionally ignored, yo could use _ = execution.Task.Wait(rate);
to explicitly state the intention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hah, this is a strange thing to do :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like to be explicit :)
I haven't tested it out, but this should help silence static analysers reporting unused return values, when I intentionally didn't use it.
So it looks like everything is fixed! Is there anything more I should add? |
As soon as @jnyrup approves the PR, it'll get merged. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, only one change needed.
The minor and suggestions are up to you.
//----------------------------------------------------------------------------------------------------------- | ||
// Act | ||
//----------------------------------------------------------------------------------------------------------- | ||
Action act = () => someAction.ExecutionTime().Should().BeLessThan(100.Milliseconds()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BeLessOrEqualTo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, looks like github didn't get my change, check the source peterekepeter@8ab0e1b#diff-dd0f96bbe45e01b1feafc897a60a299dL114
@@ -133,12 +164,14 @@ public void BeCloseTo(TimeSpan expectedDuration, TimeSpan precision, string beca | |||
{ | |||
var minimumValue = expectedDuration - precision; | |||
var maximumValue = expectedDuration + precision; | |||
|
|||
PollUntil(condition: elapsed => elapsed.CompareTo(maximumValue) < 0, result: false, rate: maximumValue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: extract to local variable as in the other Assertions.
//----------------------------------------------------------------------------------------------------------- | ||
act | ||
.Should().Throw<XunitException>() | ||
.And.Message.Should().StartWith( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: use WithMessage
.
E.g. WithMessage("*action should be less than 0.100s, but it required*")
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice, did not know about this one
/// If the execution runs into an exeption, then this will rethrow it. | ||
/// </summary> | ||
/// <param name="condition">Condition to check on the current elapsed time.</param> | ||
/// <param name="result">The expected result when polling is stopped.</param> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor:
Maybe it's just me, but result
indicates a computed value to me, whereas this is a stop condition.
So maybe rename to something like stopCondition
, expectedResult
or?
…stead of less than equal to.
…to expectedResult.
updated, also changed all assertions from that file to use WithMessage(), it looks readable that way, and it's consistent |
@peterekepeter I tried to poke around with the PR and noticed two things:
No tests failed when changing these two things, so I conclude that they are either compatible or we are missing a test. |
|
@jnyrup I assume you're taking care of this PR, right? |
@dennisdoomen I will. I've just been short on spare time, but hope to review the last part during the weekend. |
Don't worry. Was just checking you were not waiting for me. |
As we can now break early from a long running action, the failure message is now a bit misleading if we broke out early. E.g. Action act = () => Thread.Sleep(10.Seconds());
act.ExecutionTime().Should().BeLessThan(50.Milliseconds());
But we don't know how long it will take, we just know that it required more than 50 ms. Maybe adjust parts of the failure message based on |
…still running. Refactored PollUntil to return elapsed time and wether the action is still running or not.
Okay, I've added a "more than" in case there is an early exit, and the message will contain "exactly" if it's not an early exit. This way it does not break existing tests. So you get messages like: |
I don't understand AppVeyor's error message. I did not touch the test which failed. |
@peterekepeter, thanks for the contribution to Fluent Assertions! |
@jnyrup It was a long process, but finally! |
So? Time for another release? |
ExecutionTimeAssertions works on cases where the code deadlocks, tests finish as soon as conditions are met or failed instead of running indefinitely.
I implemented the feature where you can have test cases can run indefinitely or deadlock. The assertions will only wait until necessary for them to determine if test passes or fails, and they continue on. The only side effect is that if the thread runs indefinitely or deadlocks then the thread will stay hanging, but then again in these cases your test should fail.
IMPORTANT
master
branch.