Skip to content
This repository was archived by the owner on Feb 2, 2021. It is now read-only.

Commit 623c2d8

Browse files
Merge pull request #1043 from telerik/vladimirov/fix-sim-debug-brk
feat(net): Add method to check if port is in LISTEN state
2 parents 1ae842e + a3beed4 commit 623c2d8

File tree

4 files changed

+248
-1
lines changed

4 files changed

+248
-1
lines changed

declarations.d.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1736,6 +1736,25 @@ interface IVersionData {
17361736
patch: string;
17371737
}
17381738

1739+
interface IWaitForPortListenData {
1740+
/**
1741+
* Port to be checked.
1742+
* @type {number}
1743+
*/
1744+
port: number;
1745+
1746+
/**
1747+
* Max amount of time in milliseconds to wait.
1748+
* @type {number}
1749+
*/
1750+
timeout: number;
1751+
/**
1752+
* @optional The amount of time between each check.
1753+
* @type {number}
1754+
*/
1755+
interval?: number;
1756+
}
1757+
17391758
/**
17401759
* Wrapper for net module of Node.js.
17411760
*/
@@ -1761,6 +1780,13 @@ interface INet {
17611780
* @return {Promise<boolean>} true if the port is available.
17621781
*/
17631782
isPortAvailable(port: number): Promise<boolean>;
1783+
1784+
/**
1785+
* Waits for port to be in LISTEN state.
1786+
* @param {IWaitForPortListenData} waitForPortListenData Data describing port, timeout and interval.
1787+
* @returns {boolean} true in case port is in LISTEN state, false otherwise.
1788+
*/
1789+
waitForPortToListen(waitForPortListenData: IWaitForPortListenData): Promise<boolean>;
17641790
}
17651791

17661792
interface IProcessService {
@@ -1950,6 +1976,12 @@ interface IOsInfo {
19501976
* @return {string} A string identifying the operating system bitness.
19511977
*/
19521978
arch(): string;
1979+
1980+
/**
1981+
* Returns a string identifying the operating system platform.
1982+
* @return {string} A string identifying the operating system platform.
1983+
*/
1984+
platform(): string;
19531985
}
19541986

19551987
interface IPromiseActions<T> {

os-info.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export class OsInfo implements IOsInfo {
1212
public arch(): string {
1313
return os.arch();
1414
}
15+
16+
public platform(): string {
17+
return os.platform();
18+
}
1519
}
1620

1721
$injector.register("osInfo", OsInfo);

services/net-service.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import * as net from "net";
2+
import { sleep } from "../helpers";
23

34
export class Net implements INet {
4-
constructor(private $errors: IErrors) { }
5+
private static DEFAULT_INTERVAL = 1000;
6+
7+
constructor(private $errors: IErrors,
8+
private $childProcess: IChildProcess,
9+
private $logger: ILogger,
10+
private $osInfo: IOsInfo) { }
511

612
public async getFreePort(): Promise<number> {
713
const server = net.createServer((sock: string) => { /* empty - noone will connect here */ });
@@ -71,6 +77,59 @@ export class Net implements INet {
7177

7278
return startPort;
7379
}
80+
81+
public async waitForPortToListen(waitForPortListenData: IWaitForPortListenData): Promise<boolean> {
82+
if (!waitForPortListenData) {
83+
this.$errors.failWithoutHelp("You must pass port and timeout for check.");
84+
}
85+
86+
const { timeout, port } = waitForPortListenData;
87+
const interval = waitForPortListenData.interval || Net.DEFAULT_INTERVAL;
88+
89+
const endTime = new Date().getTime() + timeout;
90+
const platformData: IDictionary<{ command: string, regex: RegExp }> = {
91+
"darwin": {
92+
command: "netstat -f inet -p tcp -anL",
93+
regex: new RegExp(`\\.${port}\\b`, "g")
94+
},
95+
"linux": {
96+
command: "netstat -tnl",
97+
regex: new RegExp(`:${port}\\s`, "g")
98+
},
99+
"win32": {
100+
command: "netstat -ant -p tcp",
101+
regex: new RegExp(`TCP\\s+(\\d+\\.){3}\\d+:${port}.*?LISTEN`, "g")
102+
}
103+
};
104+
105+
const platform = this.$osInfo.platform();
106+
const currentPlatformData = platformData[platform];
107+
if (!currentPlatformData) {
108+
this.$errors.failWithoutHelp(`Unable to check for free ports on ${platform}. Supported platforms are: ${_.keys(platformData).join(", ")}`);
109+
}
110+
111+
while (true) {
112+
const { command, regex } = currentPlatformData;
113+
114+
try {
115+
const result = await this.$childProcess.exec(command);
116+
if (result && !!result.match(regex)) {
117+
return true;
118+
}
119+
} catch (err) {
120+
this.$logger.trace(`Error while calling '${command}': ${err}`);
121+
}
122+
123+
const currentTime = new Date().getTime();
124+
if (currentTime >= endTime) {
125+
break;
126+
}
127+
128+
await sleep(interval);
129+
}
130+
131+
return false;
132+
}
74133
}
75134

76135
$injector.register("net", Net);
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { Net } from "../../../services/net-service";
2+
import { assert } from "chai";
3+
import { Yok } from "../../../yok";
4+
import { ErrorsStub, CommonLoggerStub } from "../stubs";
5+
import { EOL } from "os";
6+
7+
describe("net", () => {
8+
const createTestInjector = (platform: string): IInjector => {
9+
const testInjector = new Yok();
10+
testInjector.register("errors", ErrorsStub);
11+
testInjector.register("childProcess", {});
12+
testInjector.register("logger", CommonLoggerStub);
13+
testInjector.register("osInfo", {
14+
platform: () => platform
15+
});
16+
17+
return testInjector;
18+
};
19+
20+
describe("waitForPortToListen", () => {
21+
let execCalledCount = 0;
22+
beforeEach(() => {
23+
execCalledCount = 0;
24+
});
25+
26+
const createNetStatResult = (testInjector: IInjector, platform: string, port?: number, iteration?: number): void => {
27+
const childProcess = testInjector.resolve<IChildProcess>("childProcess");
28+
29+
childProcess.exec = async (command: string, options?: any, execOptions?: IExecOptions): Promise<any> => {
30+
const platformsData: IDictionary<any> = {
31+
linux: {
32+
data: `Active Internet connections (only servers)
33+
Proto Recv-Q Send-Q Local Address Foreign Address State
34+
tcp 0 0 192.168.122.1:53 0.0.0.0:* LISTEN
35+
tcp 0 0 127.0.1.1:53 0.0.0.0:* LISTEN
36+
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
37+
tcp6 0 0 :::60433 :::* LISTEN
38+
tcp6 0 0 ::1:631 :::* LISTEN`,
39+
portData: `tcp6 0 0 :::${port} :::* LISTEN`
40+
},
41+
42+
darwin: {
43+
data: `Current listen queue sizes (qlen/incqlen/maxqlen)
44+
Listen Local Address
45+
0/0/1 127.0.0.1.9335
46+
0/0/1 127.0.0.1.9334
47+
0/0/1 127.0.0.1.9333
48+
0/0/128 *.3283
49+
0/0/128 *.88
50+
0/0/128 *.22`,
51+
portData: `0/0/128 *.${port}`
52+
},
53+
win32: {
54+
data: `
55+
Active Connections
56+
57+
Proto Local Address Foreign Address State
58+
TCP 0.0.0.0:80 0.0.0.0:0 LISTENING
59+
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING
60+
TCP 0.0.0.0:60061 0.0.0.0:0 LISTENING
61+
TCP 127.0.0.1:5037 127.0.0.1:54737 ESTABLISHED
62+
TCP 127.0.0.1:5037 127.0.0.1:54741 ESTABLISHED
63+
TCP 127.0.0.1:5354 0.0.0.0:0 LISTENING`,
64+
portData: ` TCP 127.0.0.1:${port} 0.0.0.0:0 LISTENING`
65+
}
66+
};
67+
68+
execCalledCount++;
69+
70+
let data = platformsData[platform].data;
71+
72+
if (port) {
73+
data += `${EOL}${platformsData[platform].portData}`;
74+
}
75+
76+
if (iteration) {
77+
return iteration === execCalledCount ? data : "";
78+
}
79+
return data;
80+
};
81+
};
82+
83+
_.each(["linux", "darwin", "win32"], platform => {
84+
describe(`for ${platform}`, () => {
85+
it("returns true when netstat returns port is listening", async () => {
86+
const port = 18181;
87+
const testInjector = createTestInjector(platform);
88+
createNetStatResult(testInjector, platform, port);
89+
90+
const net = testInjector.resolve<INet>(Net);
91+
const isPortListening = await net.waitForPortToListen({ port, timeout: 10, interval: 1 });
92+
assert.isTrue(isPortListening);
93+
assert.equal(execCalledCount, 1);
94+
});
95+
96+
it("returns false when netstat does not return the port as not listening", async () => {
97+
const testInjector = createTestInjector(platform);
98+
createNetStatResult(testInjector, platform);
99+
100+
const net = testInjector.resolve<INet>(Net);
101+
const isPortListening = await net.waitForPortToListen({ port: 18181, timeout: 5, interval: 1 });
102+
assert.isFalse(isPortListening);
103+
assert.isTrue(execCalledCount > 1);
104+
});
105+
106+
it("returns true when netstat finds the port after some time", async () => {
107+
const port = 18181;
108+
const testInjector = createTestInjector(platform);
109+
const iterations = 2;
110+
createNetStatResult(testInjector, platform, port, iterations);
111+
112+
const net = testInjector.resolve<INet>(Net);
113+
const isPortListening = await net.waitForPortToListen({ port, timeout: 10, interval: 1 });
114+
assert.isTrue(isPortListening);
115+
assert.equal(execCalledCount, iterations);
116+
});
117+
118+
it("returns false when netstat command fails", async () => {
119+
const testInjector = createTestInjector(platform);
120+
const childProcess = testInjector.resolve<IChildProcess>("childProcess");
121+
const error = new Error("test error");
122+
childProcess.exec = async (command: string, options?: any, execOptions?: IExecOptions): Promise<any> => {
123+
execCalledCount++;
124+
return Promise.reject(error);
125+
};
126+
127+
const net = testInjector.resolve<INet>(Net);
128+
const isPortListening = await net.waitForPortToListen({ port: 18181, timeout: 50, interval: 1 });
129+
assert.isFalse(isPortListening);
130+
assert.isTrue(execCalledCount > 1);
131+
});
132+
});
133+
});
134+
135+
it("throws correct error when current operating system is not supported", async () => {
136+
const invalidPlatform = "invalid_platform";
137+
const testInjector = createTestInjector(invalidPlatform);
138+
139+
const net = testInjector.resolve<INet>(Net);
140+
await assert.isRejected(net.waitForPortToListen({ port: 18181, timeout: 50, interval: 1 }), `Unable to check for free ports on ${invalidPlatform}. Supported platforms are: darwin, linux, win32`);
141+
});
142+
143+
it("throws correct error when null is passed", async () => {
144+
const invalidPlatform = "invalid_platform";
145+
const testInjector = createTestInjector(invalidPlatform);
146+
147+
const net = testInjector.resolve<INet>(Net);
148+
await assert.isRejected(net.waitForPortToListen(null), "You must pass port and timeout for check.");
149+
150+
});
151+
});
152+
});

0 commit comments

Comments
 (0)