-
Notifications
You must be signed in to change notification settings - Fork 456
Use asynchronous childProcess commands in Network tests - Closes #2334 #2382
Conversation
… to be asynchronous
test/network/scenarios/common.js
Outdated
@@ -17,6 +17,9 @@ | |||
const childProcess = require('child_process'); | |||
const utils = require('../utils'); | |||
|
|||
const NODE_READY_REGEX = /Finished sync/; |
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 you want to check loading status by log message then better would be Blockchain ready
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 I only wait for 'Blockchain ready', it breaks the test since it doesn't give enough time for all the nodes to sync up.
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.
The name of regular expression you are using is NODE_READY_REGEX
https://github.com/LiskHQ/lisk/blob/ed88a37008d92b82260d86db956ec543c5c71eca/test/network/scenarios/common.js#L20
Which clearly is not the case, you are not waiting for the node to be ready, you are waiting for node to be synced at least once. If that's the case please use proper naming for the variables.
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 am also not a big fan of grepping the log message to verify if the node is ready, the reliable way is to either subscribe to the blockchainReady
event which clearly indicated the blockchain is loaded or using the API endpoint to check if the syncing is true or false and make the decision based on the boolean result.
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.
Which clearly is not the case
It's not so black and white. 'Node ready' can mean a lot of things depending on whether we're looking at it from the perspective of the node or from the perspective of the test.
I'll rename to NODE_FINISHED_SYNC_REGEX
to avoid any confusion.
pm2LogProcess.stdout.removeAllListeners('data'); | ||
resolve(); | ||
} | ||
}); |
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.
Better practice to handle this use case would be to use pm2
builtin feature for it. PM2 have a CLI option –wait-ready
and then from the process you can send an event process.send('ready')
to notify PM2 that app is ready.
This would be the best way to in the core and then either PM2 or some other process manager can also use it.
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 didn't know about this feature. I'll give it a try.
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.
That feature would be perfect but I can't get it to work for some reason.
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.
May be we can sit together to check it out.
}) | ||
.catch(err => { | ||
done(err.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.
I believe this code will have similar impact.
before(done => {
common.stopNode('node_1').then(done).catch(done);
});
or simply returning the promise will also work here.
before(() => common.stopNode('node_1'))
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.
The suggested approach looks cleaner but it's more brittle; if the stopNode()
function changes in the future (e.g. returns a value from the promise), it could break stuff in an unexpected way. I think it's better to be explicit about what is passed to the done
function.
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 stopNode
change in the future we should should its usage. So why not use the cleaner approach which fits in current circumstances.
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.
A function should be a blackbox as much as possible. The stopNode
function shouldn't have to worry about how its output is being used by dependent functions (done
in this case). That's why the extra fat-arrow functions are important in this case. It's also more readable because looking at the test you can see exactly what parameters go into the done
function without having to dig into the implementation details of the stopNode
function.
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.
There is nothing to do with internal functionality of the function.
Yes function should be blackbox but also follow the documented interface. If its documented that the function will return a Promise then it should always return promise. And all relevant code should be written in that perspective that it is returning the Promise.
And once function return type is change, its reference usage must be updated accordingly.
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'll do it since most people seem to disagree with me :'(
}) | ||
.catch(err => { | ||
done(err.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.
Same here.
}) | ||
.catch(err => { | ||
done(err.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.
This would be much compact option.
it('stop all the nodes in the network except node_0', () => {
return Promise.map(new Array(TOTAL_PEERS), (value, index) => {
return common.stopNode(`node_${index}`);
});
});
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.
That wouldn't work. There is no node_10
- We need to loop from node_0 to node_9.
Also, a for
loop is easier to read in this case.
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.
Just remove the +1
from the index.
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.
But then it stops node_0
which we want to keep running.
}) | ||
.catch(err => { | ||
done(err.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.
Same here.
test/network/scenarios/common.js
Outdated
@@ -17,6 +17,9 @@ | |||
const childProcess = require('child_process'); | |||
const utils = require('../utils'); | |||
|
|||
const NODE_READY_REGEX = /Finished sync/; |
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 am also not a big fan of grepping the log message to verify if the node is ready, the reliable way is to either subscribe to the blockchainReady
event which clearly indicated the blockchain is loaded or using the API endpoint to check if the syncing is true or false and make the decision based on the boolean result.
test/network/scenarios/common.js
Outdated
@@ -17,6 +17,9 @@ | |||
const childProcess = require('child_process'); | |||
const utils = require('../utils'); | |||
|
|||
const NODE_READY_REGEX = /Finished sync/; | |||
const NODE_READY_TIMEOUT = 20000; |
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.
the disadvantage of having the timeout as a way to check if the application starts within this time frame is that it will be indeterministic if the performance of the application degrades then the tests will fail and if the performance improves then still it has to wait 20 seconds, as I said before if we subscribe to blockchainReady
event then regardless of the time the tests will be consistent.
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.
The timeout specifies the upper bound; there needs to be a timeout in case the node is taking way too long to launch (due to an unforeseen issue with the node itself); then it should fail the test with an error. It should be a large value though. I can make it higher. 20s is a bit short.
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 made it 40 seconds.
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 agree that it's not the best but we're running the node with PM2 so we can't listen to the node's events directly. I investigated using the HTTP API to check the sync status of the node but that would add a lot of complexity because of how we're running the nodes, it's difficult to associate them with a URL.
5fc911f
to
55fc17d
Compare
test/network/scenarios/common.js
Outdated
let pm2LogProcess = childProcess.spawn('pm2', ['logs', nodeName]); | ||
pm2LogProcess.once('error', err => { | ||
clearTimeout(nodeReadyTimeout); | ||
pm2LogProcess.stdout.removeAllListeners('data'); |
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 not remove error listener here as well?
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.
It uses the once('error', ...)
handler so if the handler executes once then it gets removed implicitly. I can change it to on('error', ...)
and then explicitly remove the 'error' event handler if you prefer.
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.
Also, based on your earlier feedback, I moved up the let pm2LogProcess = ...
declaration since that's more clear. Also I made it const
.
@@ -17,6 +17,9 @@ | |||
const childProcess = require('child_process'); | |||
const utils = require('../utils'); | |||
|
|||
const NODE_FINISHED_SYNC_REGEX = /Finished sync/; |
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 are we using Finished sync
as a regular expression?
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 doing regex.test(...)
instead of string.indexOf(...) !== ...
it really doesn't matter for me though.
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 added a comment in the code.
Addressed key issues and justified the ones which couldn't be resolved.
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 am approving the PR as its hanging for long. But I am not convinced with the approach to wait for syncing as part of the node startup.
That particular part should be done explicitly only in the cases where needed. With detail comments that why we need that in that particular test case.
common | ||
.stopNode('node_1') | ||
.then(done) | ||
.catch(done); |
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.
Please check if pm2 kill
is being executed after the tests failed.
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.
We were running the network tests with the mocha --bail
flag which meant that our after()
hook was skipped (when an error was thrown inside our before()
hook) and so the test suite would not cleanup after itself.
This is an open issue with mocha: mochajs/mocha#3398
Removing the --bail
flag fixed the issue.
… after() hook from running which prevents cleanup of nodes
The purpose of the
The best way to detect if the tests have finished is to use Mocha's |
What was the problem?
How did I fix it?
How to test it?
grunt mocha:default:network
Review checklist