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 tests setup - ensure errors are exposed #4139
Conversation
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.
Wow! That's a really nice catch!
Thanks for that @medikoo 👍
We need to fix this ASAP. Big +1 for adding a global Promise catch statement. AFAIK we had that previously, but we removed this in Oct / Nov 2016 (AFAIK it had smth. todo with invoke
/ invoke local
). Can you remember what this was @mthenw @eahefnawy @nikgraf ?
IMHO we should re-add this ASAP. Furthermore it would be nice if we could fix the tests in this PR so that we're in a passing state after merging this
(otherwise reviewing and merging new PRs might be a nightmare).
Looks like we already have smth. like that in place --> here. It will execute this method. However it's not executed correctly (looks like the exit code is not set correctly). |
This program is not executed (required) when testing, which probably makes sense, but then it means that |
@pmuens The problem with invoke local is, that customer code is executed within the Serverless process. That means that, if the frameworks adds a specific unhandled rejection or unhandled exception handler, it must make sure that the tracker is not invoked in case of The other side of this is, that the unhandled handler should in this case not interfere with handlers that the user code might have set. Imagine user code that handles them in some obscure way and just continues execution somehow. Maybe the handler could be set conditionally in the framework, depending on the commands invoked (i.e. not for |
@HyperBrain does your points also relate to tests process in any way? I totally agree it should not be forced within Serverless in general, but there's no side effects to the users of the framework if unhandled rejections are forced to crash purely in program that runs tests (or am I missing something?) |
I referred to @pmuens
Locally within the tests I see no problem at all. Only if the handling leaves that scope care has to be taken 😃 |
Ideally this should be taken care in mocha, and it's already been proposed and discussed there: mochajs/mocha#1926 For a meantime we may take care of it via slight hacking, introduce #!/usr/bin/env node
'use strict';
process.on('unhandledRejection', err => {
throw err;
});
require('mocha/bin/_mocha'); And referring scripts to this program instead of |
Thanks for the nice conversation @HyperBrain and @medikoo 👍! I like the solution to introduce a |
@pmuens I thought it might be good to also hard code here arguments:
It can work as default, so if some arguments are detected, then they're not forced It will allow, to not have them duplicated in scripts configuration What do you think? |
@medikoo yes, that sounds like a good enhancement! This way we'd have one place to look for / make changes 👍 |
@pmuens I applied that. I've also added |
Very nice! Thanks for the updates @medikoo 👍! |
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.
Thanks for the updates @medikoo 👍 💯
I just gave this another try today and it looks pretty promising. Furthermore I added some comments while doing a code-review.
When I run it I get a pretty weird output. The result is duplicated. I've attached a screenshot which shows how it looks like on my machine after running npm test
:
lib/Serverless.test.js
Outdated
|
||
afterEach(() => { | ||
if (!serverless.utils.logStat.restore) return; |
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.
Why is this needed?
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.
As serverless.init()
fails, stubs are not initialized therefore there's no restore
methods.
Without this, there will be additional crash in afterEach
.
Still it's indeed not needed when serverless.init()
succeeds, so assuming we're fix it, we can indeed remove it, as it's just extra noise
lib/classes/Utils.test.js
Outdated
@@ -21,7 +21,7 @@ describe('Utils', () => { | |||
beforeEach(() => { | |||
serverless = new Serverless(); | |||
utils = new Utils(serverless); | |||
serverless.init(); | |||
return serverless.init(); |
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 we put this right at the beginning of the beforeEach
block like it's done below?
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.
You mean to move it before utils = new Utils(serverless);
line? It can't be starting line here, as there's no serverless
instance yet
lib/classes/Utils.test.js
Outdated
|
||
afterEach(() => { | ||
if (!segment.track.restore) return; |
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.
Why is this needed?
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.
Same case as commented above
lib/utils/fs/writeFile.test.js
Outdated
@@ -16,7 +16,7 @@ describe('#writeFile()', function () { | |||
this.timeout(0); | |||
beforeEach(() => { | |||
serverless = new Serverless(); | |||
serverless.init(); | |||
return serverless.init(); |
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 we move this to the beginning of the beforeEach
block?
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.
Here similarly, at beginning of this beforeEach
block, there's no serverless
instance
lib/utils/fs/writeFileSync.test.js
Outdated
@@ -11,7 +11,7 @@ describe('#writeFileSync()', () => { | |||
|
|||
beforeEach(() => { | |||
serverless = new Serverless(); | |||
serverless.init(); | |||
return serverless.init(); |
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 we move this to the beginning of the beforeEach
block?
@pmuens Thanks, I replied to comments
This is result of another unhandled rejection that got in after I proposed this PR (must came in after merge with master). I fixed those with d6b29ab Now, concerning weird output by mocha. The crashing now unhandled rejections are picked by It's actually how I picked all of them cleanly, for this PR. We can achieve it by, clearing all process.on('unhandledRejection', err => {
delete process._events.uncaughtException;
throw err;
}); |
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.
Hey @medikoo,
first of all: Thanks for replying to the comments and pushing some fixes 👍.
I just took a deeper dive into this PR today since I wanted to fix all the tests so that we can merge it ASAP and have a solid test suite again.
However unfortunately the tests which were updated in this PR fail due to a timeout (e.g. should load serverless.yaml from filesystem
). I tried to work out what the main reason is, but couldn't wrap my head around it since all the Promises are returned 🤔
Do you have any clue what could be wrong here? Thanks in advance!
@pmuens interesting, I do not observe it on my side. I've also merged it with master and pursued repository cleanup with Do you have some changes that are maybe not pushed into this branch? Also maybe try |
Hey @medikoo thanks for the reply and thanks for updating this PR 👍 The failing tests on my local machine are the same which are also failing on Travis (https://travis-ci.org/serverless/serverless/jobs/270768136#L2970). I tried to dig deeper into that (used |
|
||
'use strict'; | ||
|
||
process.on('unhandledRejection', err => { |
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 think this is wrong for the test suite.
The problem here is, that it might prevent chai
from testing rejections correctly. The tests and the run code must not set any unhandledRejection
handler - more exactly, it must not turn rejections into exceptions.
It is very important that chai-as-promised
continues after receiving an exception, because for .to.be.rejectedWith()
tests the rejection will not be detected anymore by chai. This fails currently:
return expect(myFunctionThatShouldReject())
.to.be.rejectedWith(/my error message/);
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.
@HyperBrain this is about unhandled promise rejections, so ones not handled, either by chai-as-promised
or mocha
or anything else.
What's in your example is promise that's obviously handled (passed to expect
which via rejectedWith
is handled by chai-as-promised
), you can easily validate that by e.g. adding test as:
it.only('test', () =>
expect(Promise.reject(new Error('Rejection'))).to.be.rejectedWith('Rejection'));
It'll pass as expected, and it won't be emitted as unhandled rejection.
Example of unhandled rejection would be a promise not handled by any entity, so: not passed to any promise processing function and not returned to any promise processing consumer, as e.g.:
it.only('test', () => {
Promise.reject(new Error('Rejection'));
});
@pmuens It's interesting, as again I cannot reproduce that. Can you post diff of your changes against this branch? In my case when I have I actually need to add So e.g. after applying following diff: diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js
index b101845c..3f0abbfa 100644
--- a/lib/classes/Service.test.js
+++ b/lib/classes/Service.test.js
@@ -123,7 +123,7 @@ describe('Service', () => {
return expect(noService.load()).to.eventually.resolve;
});
- it('should load serverless.yml from filesystem', () => {
+ it.only('should load serverless.yml from filesystem', () => {
const SUtils = new Utils();
const serverlessYml = {
service: 'new-service',
@@ -181,7 +181,7 @@ describe('Service', () => {
});
});
- it('should load serverless.yaml from filesystem', () => {
+ it.only('should load serverless.yaml from filesystem', () => {
const SUtils = new Utils();
const serverlessYml = {
service: 'new-service', I have test output as: I run |
Thanks for taking a deep dive into that @medikoo 👍 💯 I'm running your branch with BTW. I'm running it on Linux (in Docker). |
@medikoo @pmuens I did a
I verified that with the commit immediately before, the unit tests still worked on my local Ubuntu, and this commit leads to timeouts for nearly all file operation tests. @pmuens identified the PR containing the commit as: #4087 Imo we should analyze it and fix this issue first, before continuing to apply further (and maybe not really needed) fixes to the unit tests, so that we really improve them. Any thoughts? |
Thanks for taking the time to bisect this @HyperBrain 👍
Yes, I 100% agree. Let's fix this first and then continue (if necessary). I'll take a look into this today. Would be super nice if we could get the tests fixed ASAP so that we can merge this and have a reliable test suite again! |
So I think I figured out what broke the tests here. In the tests for Some tests are still failing. I'm looking into those right now... |
@pmuens it's quite puzzling I'm not able to reproduce the timeout issue on my setup. You say you only experience it when you add Also on which version of node.js and linux you're observing that? I assume that it's npm v5 with dependencies installed up to |
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.
Alright. So I think all the tests have been fixed 👍
Thanks for all the hard work on this one @medikoo and @HyperBrain 💪
I'll merge it once the build passes.
@medikoo thanks for you comment (saw it now after reading through my E-Mails). 🤔 that's super odd. @HyperBrain also faced the timeout issues. Maybe it was a race condition? I've fixed all the tests in the meantime and they should be quite stable right now. Thanks for for getting this started and your work on this PR @medikoo 👍 💪 |
What did you implement:
There are errors in tests which are not properly exposed, additionally due to that, some configured tests are not invoked at all.
Issue lies in not handled rejected promises, their errors are exposed only via UnhandledRejection warnings which can be seen through the tests output, but which do not force tests to fail as expected.
I didn't fix code corresponding to broken tests, I thought it's better (more efficient) if it's looked after by its authors. Due to that CI crashes, it's expected
How did you implement it:
In tests I ensured that those rejected promises are returned to mocha functions, therefore exposed as expected.
Still, more future proof solution would be to add following code to some init test script:
This will ensure that all currently (and those introduced in future) broken tests will fail same way as they do after this PR is applied.
This approach was also scheduled to be default in Node.js -> nodejs/node#12010
If you agree on that solution I may add it instead of patching individual tests, just let me know where's the best place.
How can we verify it:
Run tests
Todos:
Is this ready for review?: YES
Is it a breaking change?: NO