Skip to content

Commit 9801377

Browse files
authored
fix: move findPort as static method on Server (#3296)
1 parent fc9028c commit 9801377

File tree

8 files changed

+187
-181
lines changed

8 files changed

+187
-181
lines changed

lib/Server.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -761,8 +761,6 @@ class Server {
761761
}
762762

763763
listen(port, hostname, fn) {
764-
const findPort = require('./utils/findPort');
765-
766764
if (hostname === 'local-ip') {
767765
this.hostname = internalIp.v4.sync() || internalIp.v6.sync() || '0.0.0.0';
768766
} else if (hostname === 'local-ipv4') {
@@ -780,7 +778,7 @@ class Server {
780778
}
781779

782780
return (
783-
findPort(port || this.options.port)
781+
Server.getFreePort(port || this.options.port)
784782
// eslint-disable-next-line no-shadow
785783
.then((port) => {
786784
this.options.port = port;
@@ -846,6 +844,35 @@ class Server {
846844
};
847845
}
848846

847+
static getFreePort(port) {
848+
const pRetry = require('p-retry');
849+
const portfinder = require('portfinder');
850+
851+
if (port && port !== 'auto') {
852+
return Promise.resolve(port);
853+
}
854+
855+
function runPortFinder() {
856+
return new Promise((resolve, reject) => {
857+
// default port
858+
portfinder.basePort = 8080;
859+
portfinder.getPort((error, foundPort) => {
860+
if (error) {
861+
return reject(error);
862+
}
863+
864+
return resolve(foundPort);
865+
});
866+
});
867+
}
868+
869+
// Try to find unused port and listen on it for 3 times,
870+
// if port is not specified in options.
871+
const defaultPortRetry = parseInt(process.env.DEFAULT_PORT_RETRY, 10) || 3;
872+
873+
return pRetry(runPortFinder, { retries: defaultPortRetry });
874+
}
875+
849876
getStats(statsObj) {
850877
const getStatsOption = require('./utils/getStatsOption');
851878

lib/utils/findPort.js

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,6 @@
11
'use strict';
22

3-
const pRetry = require('p-retry');
4-
const portfinder = require('portfinder');
3+
// TODO(@anshumanv) - Drop in next major release
4+
const getFreePort = require('../Server').getFreePort;
55

6-
function runPortFinder() {
7-
return new Promise((resolve, reject) => {
8-
// default port
9-
portfinder.basePort = 8080;
10-
portfinder.getPort((error, port) => {
11-
if (error) {
12-
return reject(error);
13-
}
14-
15-
return resolve(port);
16-
});
17-
});
18-
}
19-
20-
function findPort(port) {
21-
if (port && port !== 'auto') {
22-
return Promise.resolve(port);
23-
}
24-
25-
// Try to find unused port and listen on it for 3 times,
26-
// if port is not specified in options.
27-
const defaultPortRetry = parseInt(process.env.DEFAULT_PORT_RETRY, 10) || 3;
28-
29-
return pRetry(runPortFinder, { retries: defaultPortRetry });
30-
}
31-
32-
module.exports = findPort;
6+
module.exports = getFreePort;

test/server/Server.test.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
'use strict';
22

33
const { relative, sep } = require('path');
4+
const http = require('http');
45
const webpack = require('webpack');
56
const sockjs = require('sockjs/lib/transport');
7+
// TODO(@anshumanv) - Remove this test in next major
8+
const findPort = require('../../lib/utils/findPort');
69
const Server = require('../../lib/Server');
710
const config = require('../fixtures/simple-config/webpack.config');
811
const port = require('../ports-map').Server;
912
const isWebpack5 = require('../helpers/isWebpack5');
1013

14+
const getFreePort = Server.getFreePort;
1115
jest.mock('sockjs/lib/transport');
1216

1317
const baseDevConfig = {
@@ -363,4 +367,150 @@ describe('Server', () => {
363367
expect(process.env.WEBPACK_SERVE).toBe(true);
364368
});
365369
});
370+
371+
describe('getFreePort', () => {
372+
let dummyServers = [];
373+
374+
afterEach(() => {
375+
delete process.env.DEFAULT_PORT_RETRY;
376+
377+
return dummyServers
378+
.reduce(
379+
(p, server) =>
380+
p.then(
381+
() =>
382+
new Promise((resolve) => {
383+
server.close(resolve);
384+
})
385+
),
386+
Promise.resolve()
387+
)
388+
.then(() => {
389+
dummyServers = [];
390+
});
391+
});
392+
393+
function createDummyServers(n) {
394+
return (Array.isArray(n) ? n : [...new Array(n)]).reduce(
395+
(p, _, i) =>
396+
p.then(
397+
() =>
398+
new Promise((resolve, reject) => {
399+
const server = http.createServer();
400+
401+
dummyServers.push(server);
402+
403+
server.listen(8080 + i, () => {
404+
resolve();
405+
});
406+
407+
server.on('error', (error) => {
408+
reject(error);
409+
});
410+
})
411+
),
412+
Promise.resolve()
413+
);
414+
}
415+
416+
it('should returns the port when the port is specified', () => {
417+
process.env.DEFAULT_PORT_RETRY = 5;
418+
419+
return getFreePort(8082).then((freePort) => {
420+
expect(freePort).toEqual(8082);
421+
});
422+
});
423+
424+
it('should returns the port when the port is null', () => {
425+
const retryCount = 2;
426+
427+
process.env.DEFAULT_PORT_RETRY = 2;
428+
429+
return createDummyServers(retryCount)
430+
.then(() => getFreePort(null))
431+
.then((freePort) => {
432+
expect(freePort).toEqual(8080 + retryCount);
433+
});
434+
});
435+
436+
it('should returns the port when the port is undefined', () => {
437+
const retryCount = 2;
438+
439+
process.env.DEFAULT_PORT_RETRY = 2;
440+
441+
return (
442+
createDummyServers(retryCount)
443+
// eslint-disable-next-line no-undefined
444+
.then(() => getFreePort(undefined))
445+
.then((freePort) => {
446+
expect(freePort).toEqual(8080 + retryCount);
447+
})
448+
);
449+
});
450+
451+
it('should retry finding the port for up to defaultPortRetry times (number)', () => {
452+
const retryCount = 3;
453+
454+
process.env.DEFAULT_PORT_RETRY = retryCount;
455+
456+
return createDummyServers(retryCount)
457+
.then(() => getFreePort())
458+
.then((freePort) => {
459+
expect(freePort).toEqual(8080 + retryCount);
460+
});
461+
});
462+
463+
it('should retry finding the port for up to defaultPortRetry times (string)', () => {
464+
const retryCount = 3;
465+
466+
process.env.DEFAULT_PORT_RETRY = `${retryCount}`;
467+
468+
return createDummyServers(retryCount)
469+
.then(() => getFreePort())
470+
.then((freePort) => {
471+
expect(freePort).toEqual(8080 + retryCount);
472+
});
473+
});
474+
475+
// TODO: fix me, Flaky on CI
476+
it.skip('should retry finding the port when serial ports are busy', () => {
477+
const busyPorts = [8080, 8081, 8082, 8083];
478+
479+
process.env.DEFAULT_PORT_RETRY = 3;
480+
481+
return createDummyServers(busyPorts)
482+
.then(() => getFreePort())
483+
.then((freePort) => {
484+
expect(freePort).toEqual(8080 + busyPorts.length);
485+
});
486+
});
487+
488+
it("should throws the error when the port isn't found", () => {
489+
expect.assertions(1);
490+
491+
jest.mock('portfinder', () => {
492+
return {
493+
getPort: (callback) => callback(new Error('busy')),
494+
};
495+
});
496+
497+
const retryCount = 1;
498+
499+
process.env.DEFAULT_PORT_RETRY = 0;
500+
501+
return createDummyServers(retryCount)
502+
.then(() => getFreePort())
503+
.catch((err) => {
504+
expect(err.message).toMatchSnapshot();
505+
});
506+
});
507+
508+
it('should work with findPort util', () => {
509+
process.env.DEFAULT_PORT_RETRY = 5;
510+
511+
return findPort(8082).then((freePort) => {
512+
expect(freePort).toEqual(8082);
513+
});
514+
});
515+
});
366516
});

test/server/__snapshots__/Server.test.js.snap.webpack4

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,5 @@ Array [
5959
],
6060
]
6161
`;
62+
63+
exports[`Server getFreePort should throws the error when the port isn't found 1`] = `"busy"`;

test/server/__snapshots__/Server.test.js.snap.webpack5

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,5 @@ Array [
5959
],
6060
]
6161
`;
62+
63+
exports[`Server getFreePort should throws the error when the port isn't found 1`] = `"busy"`;

test/server/utils/__snapshots__/findPort.test.js.snap.webpack4

Lines changed: 0 additions & 3 deletions
This file was deleted.

test/server/utils/__snapshots__/findPort.test.js.snap.webpack5

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)