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
Serialize Executor events #1054
Conversation
Some of the timeout issues may be happening because |
Actually, it looks like the root problem is the coverage event that gets emitted when the top-level suite is finished, which ends up calling |
When I tweak |
'error', | ||
'log' | ||
].includes(<string>eventName); | ||
const needsSeparateNotificationChain = |
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.
Instead of singling out coverage
, error
and log
here and calling it needsSeparateNotificationChain
, maybe we should name it after the actual events that are part of the test run. E.g. something like isTestLifeCycleEvent
. Does that make sense?
Codecov Report
@@ Coverage Diff @@
## 4.x #1054 +/- ##
==========================================
+ Coverage 67.01% 67.07% +0.05%
==========================================
Files 60 60
Lines 4918 4923 +5
Branches 1101 1104 +3
==========================================
+ Hits 3296 3302 +6
+ Misses 1622 1621 -1
Continue to review full report at Codecov.
|
I haven't forgotten this, just haven't had a chance to look at it lately... |
Thanks for letting me know. In the end it is not actually a big change. |
So, the PR does seem like it would prevent the deadlock from recursive emit calls. However...in looking at the event emitting and handling code, I seem to have forgotten exactly why the original serialization problem is occurring (the downside of long breaks while solving a problem). I mean, const start = () => {
return this.executor.emit('suiteStart', this).then(function () {
startTime = Date.now();
});
};
...
task = this.publishAfterSetup
? before().then(start)
: start().then(before);
return task.then(() => {
...
}).finally(() => (this.publishAfterSetup ? end() : after())); In Suite, the task chain is basically |
The problem occurs in the intern-cucumber integration, where the event runner is using Cucumber.js's runtime to drive the tests. This means it can't ensure that the emit-calls are completely handled before a new test step is started. An alternative would be try and solve it in the intern-cucumber integration code. I tried this as the first step, but I do not remember exactly why that did not work anymore, maybe I just did not understand it well enough before. (At least I wasn't aware that there was a requirement to wait for the emit to finish before starting the next step. I'm afraid that will be near impossible for this integration. Come to think of it, that will screw up the elapsed time recording :( .) This PR fixes the problem in a way that at least the emit calls will be handled in the right order, ensuring that the reporters work correctly for the intern-cucumber integration. |
Ah, that's right, because it's Cucumber causing the events to be emitted rather than the Intern test runner.
What, specifically, is the problem that needs to be solved? I mean, intern-cucumber emits events, but it doesn't look like it's listening to any Intern events, so why is Intern's event-handling behavior an issue? The Cucumber engine itself doesn't wait for event handlers to complete, so a
It's not necessarily a requirement, but it's how Intern's test runner behaves. Code can wait for events to resolve, or not. Intern's test runner does wait for them, because it ensures that a remote test runner stays in sync with a local test runner (if desired), and can allow errors in event handlers to affect the testing process.
Well... that may already be an issue. For example, intern-cucumber's Timing is a good point to bring up, and one I hadn't initially considered. If we make event emits synchronous with each other, rather than having them execute immediately when code calls |
Exactly this is what is happening. When emitting the The PR change causes the new
... and because of that the reporters are able to do their job correctly.
Indeed. This means I should revisit this and probably put the timestamp handling code outside the Intern event handling chains (i.e. before emitting the
I'm not exactly sure what you mean with "if we make event emits synchronous with each other". Currently, an event handler chain is created, which -- depending on what the various reporters do -- might also be arbitrarily long than thus break any code listening to Intern events for timing information. |
The problem is that calls to individual event listeners are being chained. This means that any given listener may not be called when an event is emitted, but rather after all previous listeners have finished processing. The delays due to chained listener calls are what allows listener calls to become interleaved between for (const listener of listeners) {
notifications = notifications
.then(() => Task.resolve(listener(data)))
.then(handleErrorEvent)
.catch(handleListenerError);
}
// ...
return notifications; Instead of chaining the const notifications = [];
for (const listener of listeners) {
notifications.push(
Task.resolve(listener(data))
.then(handleErrorEvent)
.catch(handleListenerError)
);
}
// ...
return Promise.all(notifications);
I mean, event listener calls should be synchronous with when events are emitted. When a test fires a |
Ah, ok. I see what you mean now. |
I pushed a potential solution to an |
Ran it against our full suite of tests and could not find anything grouped incorrectly. |
Are you planning to make this available as 4.8.1 (or 4.9)? |
Yes, in 4.8.1, hopefully today at some point. |
`Executor#emit` waits for listeners to resolve, which allows Intern to stay in sync with event processing code. However, calls to individual listeners don't need to wait on each other. This can cause event handlers to become interleaved (e.g., a `testStart` handler might be called before a `testEnd` handler for the previous test had been called.) references #1054 resolves #1124
`Executor#emit` waits for listeners to resolve, which allows Intern to stay in sync with event processing code. However, calls to individual listeners don't need to wait on each other. This can cause event handlers to become interleaved (e.g., a `testStart` handler might be called before a `testEnd` handler for the previous test had been called.) references #1054 resolves #1124
Closing this as it was resolved by the fix for #1124 |
`Executor#emit` waits for listeners to resolve, which allows Intern to stay in sync with event processing code. However, calls to individual listeners don't need to wait on each other. This can cause event handlers to become interleaved (e.g., a `testStart` handler might be called before a `testEnd` handler for the previous test had been called.) references #1054 resolves #1124
`Executor#emit` waits for listeners to resolve, which allows Intern to stay in sync with event processing code. However, calls to individual listeners don't need to wait on each other. This can cause event handlers to become interleaved (e.g., a `testStart` handler might be called before a `testEnd` handler for the previous test had been called.) references #1054 resolves #1124
`Executor#emit` waits for listeners to resolve, which allows Intern to stay in sync with event processing code. However, calls to individual listeners don't need to wait on each other. This can cause event handlers to become interleaved (e.g., a `testStart` handler might be called before a `testEnd` handler for the previous test had been called.) references #1054 resolves #1124
Individual calls to
emit(<event>)
currently start a new asynchronous promise chain. In the intern-cucumber plugin, this causes overlapping handling of the events, since thesuiteEnd
handling is not yet finished before the newsuiteStart
event is triggered.This problem is easily fixed by chaining this
suiteStart
event at the end of thesuiteEnd
chain.