diff --git a/test/known_issues/test-dgram-bind-shared-ports-after-port-0.js b/test/known_issues/test-dgram-bind-shared-ports-after-port-0.js new file mode 100644 index 00000000000000..7b750c765ba226 --- /dev/null +++ b/test/known_issues/test-dgram-bind-shared-ports-after-port-0.js @@ -0,0 +1,56 @@ +'use strict'; +const common = require('../common'); + +// This test should fail because at present `cluster` does not know how to share +// a socket when `worker1` binds with `port: 0`, and others try to bind to the +// assigned port number from `worker1` +// +// *Note*: since this is a `known_issue` we try to swallow all errors except +// the one we are interested in + +const assert = require('assert'); +const cluster = require('cluster'); +const dgram = require('dgram'); +const BYE = 'bye'; + +if (cluster.isMaster) { + const worker1 = cluster.fork(); + + // verify that Windows doesn't support this scenario + worker1.on('error', (err) => { + if (err.code === 'ENOTSUP') throw err; + }); + + worker1.on('message', (msg) => { + if (typeof msg !== 'object') process.exit(0); + if (msg.message !== 'success') process.exit(0); + if (typeof msg.port1 !== 'number') process.exit(0); + + const worker2 = cluster.fork({ PRT1: msg.port1 }); + worker2.on('message', () => process.exit(0)); + worker2.on('exit', (code, signal) => { + // this is the droid we are looking for + assert.strictEqual(code, 0); + assert.strictEqual(signal, null); + }); + + // cleanup anyway + process.on('exit', () => { + worker1.send(BYE); + worker2.send(BYE); + }); + }); + // end master code +} else { + // worker code + process.on('message', (msg) => msg === BYE && process.exit(0)); + + // first worker will bind to '0', second will try the assigned port and fail + const PRT1 = process.env.PRT1 || 0; + const socket1 = dgram.createSocket('udp4', () => {}); + socket1.on('error', PRT1 === 0 ? () => {} : assert.fail); + socket1.bind( + { address: common.localhostIPv4, port: PRT1, exclusive: false }, + () => process.send({ message: 'success', port1: socket1.address().port }) + ); +} diff --git a/test/sequential/test-dgram-bind-shared-ports.js b/test/sequential/test-dgram-bind-shared-ports.js index 4eaf9a14d65d1d..699a82e5eb5fde 100644 --- a/test/sequential/test-dgram-bind-shared-ports.js +++ b/test/sequential/test-dgram-bind-shared-ports.js @@ -21,55 +21,93 @@ 'use strict'; const common = require('../common'); + +// This test asserts the semantics of dgram::socket.bind({ exclusive }) +// when called from a cluster.Worker + const assert = require('assert'); const cluster = require('cluster'); const dgram = require('dgram'); +const BYE = 'bye'; +const WORKER2_NAME = 'wrker2'; if (cluster.isMaster) { const worker1 = cluster.fork(); if (common.isWindows) { - const checkErrType = (er) => { - assert.strictEqual(er.code, 'ENOTSUP'); + worker1.on('error', common.mustCall((err) => { + console.log(err); + assert.strictEqual(err.code, 'ENOTSUP'); worker1.kill(); - }; - - worker1.on('error', common.mustCall(checkErrType, 1)); + })); return; } - worker1.on('message', (msg) => { + worker1.on('message', common.mustCall((msg) => { + console.log(msg); assert.strictEqual(msg, 'success'); - const worker2 = cluster.fork(); - worker2.on('message', (msg) => { - assert.strictEqual(msg, 'socket2:EADDRINUSE'); - worker1.kill(); - worker2.kill(); - }); - }); + const worker2 = cluster.fork({ WORKER2_NAME }); + worker2.on('message', common.mustCall((msg) => { + console.log(msg); + assert.strictEqual(msg, 'socket3:EADDRINUSE'); + + // finish test + worker1.send(BYE); + worker2.send(BYE); + })); + worker2.on('exit', common.mustCall((code, signal) => { + assert.strictEqual(signal, null); + assert.strictEqual(code, 0); + })); + })); + worker1.on('exit', common.mustCall((code, signal) => { + assert.strictEqual(signal, null); + assert.strictEqual(code, 0); + })); + // end master code } else { - const socket1 = dgram.createSocket('udp4', common.noop); - const socket2 = dgram.createSocket('udp4', common.noop); + // worker code + process.on('message', common.mustCallAtLeast((msg) => { + if (msg === BYE) process.exit(0); + }), 1); - socket1.on('error', (err) => { - // no errors expected - process.send(`socket1:${err.code}`); - }); + const isSecondWorker = process.env.WORKER2_NAME === WORKER2_NAME; + const socket1 = dgram.createSocket('udp4', common.mustNotCall()); + const socket2 = dgram.createSocket('udp4', common.mustNotCall()); + const socket3 = dgram.createSocket('udp4', common.mustNotCall()); - socket2.on('error', (err) => { - // an error is expected on the second worker - process.send(`socket2:${err.code}`); - }); + socket1.on('error', (err) => assert.fail(err)); + socket2.on('error', (err) => assert.fail(err)); - socket1.bind({ - address: 'localhost', - port: common.PORT, - exclusive: false - }, () => { - socket2.bind({ port: common.PORT + 1, exclusive: true }, () => { - // the first worker should succeed + // First worker should bind, second should err + const socket3OnBind = + isSecondWorker ? + common.mustNotCall() : + common.mustCall(() => { + const port3 = socket3.address().port; + assert.strictEqual(typeof port3, 'number'); process.send('success'); }); - }); + // an error is expected only in the second worker + const socket3OnError = + !isSecondWorker ? + common.mustNotCall() : + common.mustCall((err) => { + process.send(`socket3:${err.code}`); + }); + const address = common.localhostIPv4; + const opt1 = { address, port: 0, exclusive: false }; + const opt2 = { address, port: common.PORT, exclusive: false }; + const opt3 = { address, port: common.PORT + 1, exclusive: true }; + socket1.bind(opt1, common.mustCall(() => { + const port1 = socket1.address().port; + assert.strictEqual(typeof port1, 'number'); + socket2.bind(opt2, common.mustCall(() => { + const port2 = socket2.address().port; + assert.strictEqual(typeof port2, 'number'); + socket3.on('error', socket3OnError); + socket3.bind(opt3, socket3OnBind); + })); + })); }