-
Notifications
You must be signed in to change notification settings - Fork 748
Test fail when posting to SynchronizationContext.Current #3209
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
Comments
Thanks for reporting. If a test directly or indirectly causes something to be posted to the synchronization context (or thread pool, etc), the test code should wait for the outcome of that action so that it doesn't complete and potentially fail in the context of some other random test.
Should we fail your test with a more helpful message if you forget to wait for all posted work explicitly within your test, or should we silently continue all posted work and all future work posted by that work until the work dies out for a certain period of time? For example, what should happen here? A test that runs forever, or some failure like the one you're already getting but more explanatory? [Test, Apartment(ApartmentState.STA)]
public async Task ProblematicTest()
{
SynchronizationContext.Current.Post(state => DelayedWork(), null);
}
private void DelayedWork()
{
SynchronizationContext.Current.Post(state => DelayedWork(), null);
} ➡ I like the idea of explicitly waiting within your test via (The way I'm tackling this in my product code is |
(@nunit/framework-team, @glconti, and anyone watching this thread, I'm interested in your input on my questions above.) |
It is hard to say what would be the correct way to do it, because of variety of scenarios, that probably only NUnit guys can judge properly. Imo for our case the best solution would be to wait forever, ideally with some timeout exception, because in our case we are using 3rd party messaging framework (MVVM Light) that is using weak references to process message requests and once a request is processed it does a cleanup to remove dead references. And the way it does it is that if the main thread is busy (UI thread - eventhough it is in a test environment), it schedules the work for later on the synchronization context, so the intention is to fire and forget here, we don't really care when it will be done and also don't assert anything related to that cleanup. We would probably care if we were using some static shared messenger, where it would make sense to finish the cleanup to not affect other tests, but this is not our case. Currently we are using Task.Yield() after test assertion to pass the tests, which is in our view wrong, because you need to know that the tested code is using such messaging framework (which is implementation detail) and that it can fail because of that. For example if we refactor some parts of the code, which are not using messaging, to use that messaging, it can make other tests fail (this is what happend and why we filed this issue). Or on the opposite side, if we replace the messaging with some other framework which is not using synchronization context for cleanup or doesn't need that cleanup, then we would end up with "ghost" Task.Yield calls in tests. |
Hey @jnm2 thanks for the insights, (I didn't answer before as I was on vacation), I share @martinskuta's (my colleague) point of view, we'd avoid putting everywhere special code to handle an implementation detail that may change in the future. |
Your reasoning sounds good to me. I'll tackle the issue of recursively scheduled work (see the code block in #3209 (comment)) by implementing a ten second timeout starting from the moment the async method returns. The timeout will never abort the work currently executing. Rather, it will be enforced in two ways:
Would this satisfy the situations everyone is aware of? I appreciate the feedback. |
I'm leaning towards both setting the thread's test as errored and throwing an exception in both cases. We can always back off later, but otherwise we won't be brought the context to think it through. |
I think the proposed solution will work, it's perfect for our issue and it's still safe enough to prevent the recursive posting. |
Fixed in 3.12 which is just around the corner! |
Issue
Our code is relying on a third-party library which posts some action to be executed on the
SynchronizationContext.Current
, this breaks some tests that requireApartment(ApartmentState.STA)
and returnasync Task
.We managed to strip down the problem to the following tests (one is failing while the other is not).
Tested with NUnit console 3.10.0 and NUnit framework 3.11.0
Failing test
❗️ Exception
Passing test
Comments
We understood that
Task.Yield()
will force a context switch allowing for the synchronization context queue to be processed, whileTask.Result
won't. After having a look at the code and issues we saw that this is linked to #2818 and #2774The text was updated successfully, but these errors were encountered: