Skip to content

Commit 45f26dc

Browse files
committed
test(node-runtime-worker-thread): Refactor tests to make it clearer when done is called; make blocking script less aggressive
1 parent dc34ac7 commit 45f26dc

File tree

2 files changed

+76
-33
lines changed

2 files changed

+76
-33
lines changed

packages/node-runtime-worker-thread/src/spawn-child-from-source.spec.ts

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,69 @@
1-
import chai, { expect } from 'chai';
2-
import chaiAsPromised from 'chai-as-promised';
3-
import childProcess from 'child_process';
1+
import { expect } from 'chai';
2+
import childProcess, { ChildProcess } from 'child_process';
43
import { once } from 'events';
54

65
import spawnChildFromSource, { kill } from './spawn-child-from-source';
76

8-
chai.use(chaiAsPromised);
9-
107
describe('spawnChildFromSource', () => {
8+
let spawned: ChildProcess;
9+
10+
afterEach(async() => {
11+
if (spawned) {
12+
await kill(spawned, 'SIGKILL');
13+
spawned = null;
14+
}
15+
});
16+
1117
it('should resolve with a child process', async() => {
12-
const spawned = await spawnChildFromSource('');
18+
spawned = await spawnChildFromSource('');
1319
expect(spawned).to.be.instanceof((childProcess as any).ChildProcess);
14-
await kill(spawned);
1520
});
1621

1722
it('should spawn a process with an ipc channel open', async() => {
18-
const spawned = await spawnChildFromSource(
23+
spawned = await spawnChildFromSource(
1924
'process.on("message", (data) => process.send(data))'
2025
);
2126
spawned.send('Hi!');
2227
const [message] = await once(spawned, 'message');
2328
expect(message).to.equal('Hi!');
24-
await kill(spawned);
2529
});
2630

27-
it('should fail if process exited before successfully starting', () => {
28-
return expect(
29-
spawnChildFromSource(
31+
it('should fail if process exited before successfully starting', async() => {
32+
let err: Error;
33+
34+
try {
35+
spawned = await spawnChildFromSource(
3036
'throw new Error("Whoops!")',
3137
{},
3238
undefined,
3339
'ignore',
3440
'ignore'
35-
)
36-
).to.eventually.be.rejectedWith(
41+
);
42+
} catch (e) {
43+
err = e;
44+
}
45+
46+
expect(err).to.be.instanceof(Error);
47+
expect(err.message).to.match(
3748
/Child process exited with error before starting/
3849
);
3950
});
4051

41-
it('should fail if a timeout exceeded before the process is "ready"', () => {
42-
return expect(
43-
spawnChildFromSource('while(true){}', {}, 200)
44-
).to.eventually.be.rejectedWith(
52+
it('should fail if a timeout exceeded before the process is "ready"', async() => {
53+
let err: Error;
54+
55+
try {
56+
spawned = await spawnChildFromSource(
57+
'let i = 0; while(i++ < 1000000000){};',
58+
{},
59+
0
60+
);
61+
} catch (e) {
62+
err = e;
63+
}
64+
65+
expect(err).to.be.instanceof(Error);
66+
expect(err.message).to.match(
4567
/Timed out while waiting for child process to start/
4668
);
4769
});

packages/node-runtime-worker-thread/src/spawn-child-from-source.ts

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import {
1010
} from 'child_process';
1111
import { once } from 'events';
1212

13-
export async function kill(childProcess: ChildProcess, code: NodeJS.Signals | number = 'SIGTERM') {
13+
export async function kill(
14+
childProcess: ChildProcess,
15+
code: NodeJS.Signals | number = 'SIGTERM'
16+
) {
1417
childProcess.kill(code);
15-
if (
16-
childProcess.exitCode === null &&
17-
childProcess.signalCode === null
18-
) {
18+
if (childProcess.exitCode === null && childProcess.signalCode === null) {
1919
await once(childProcess, 'exit');
2020
}
2121
}
@@ -25,7 +25,7 @@ export default function spawnChildFromSource(
2525
spawnOptions: SpawnOptionsWithoutStdio = {},
2626
timeoutMs?: number,
2727
_stdout: StdioNull | StdioPipe = 'inherit',
28-
_stderr: StdioNull | StdioPipe = 'inherit',
28+
_stderr: StdioNull | StdioPipe = 'inherit'
2929
): Promise<ChildProcess> {
3030
return new Promise(async(resolve, reject) => {
3131
const readyToken = Date.now().toString(32);
@@ -37,6 +37,7 @@ export default function spawnChildFromSource(
3737

3838
if (!childProcess.stdin) {
3939
await kill(childProcess);
40+
4041
return reject(
4142
new Error("Can't write src to the spawned process, missing stdin")
4243
);
@@ -45,25 +46,44 @@ export default function spawnChildFromSource(
4546
// eslint-disable-next-line prefer-const
4647
let timeoutId: NodeJS.Timeout | null;
4748

48-
const onExit = (exitCode: number | null) => {
49+
function cleanupListeners() {
50+
if (timeoutId) {
51+
clearTimeout(timeoutId);
52+
}
53+
if (childProcess.stdin) {
54+
childProcess.stdin.off('error', onWriteError);
55+
// To swallow a possible error about writing into a closed pipe if we
56+
// e.g., killed a process before our script finished writing
57+
childProcess.stdin.on('error', () => {});
58+
}
59+
childProcess.off('message', onMessage);
60+
childProcess.off('exit', onExit);
61+
}
62+
63+
async function onExit(exitCode: number | null) {
4964
if (exitCode && exitCode > 0) {
65+
cleanupListeners();
66+
await kill(childProcess);
5067
reject(new Error('Child process exited with error before starting'));
5168
}
52-
};
69+
}
5370

54-
const onMessage = (data: Serializable) => {
71+
async function onWriteError(error: Error) {
72+
cleanupListeners();
73+
await kill(childProcess);
74+
reject(error);
75+
}
76+
77+
function onMessage(data: Serializable) {
5578
if (data === readyToken) {
56-
if (timeoutId) {
57-
clearTimeout(timeoutId);
58-
}
59-
childProcess.off('exit', onExit);
79+
cleanupListeners();
6080
resolve(childProcess);
6181
}
62-
};
82+
}
6383

6484
childProcess.on('message', onMessage);
65-
6685
childProcess.on('exit', onExit);
86+
childProcess.stdin.on('error', onWriteError);
6787

6888
childProcess.stdin.write(src);
6989
childProcess.stdin.write(`;process.send(${JSON.stringify(readyToken)})`);
@@ -72,6 +92,7 @@ export default function spawnChildFromSource(
7292
timeoutId =
7393
timeoutMs !== undefined
7494
? setTimeout(async() => {
95+
cleanupListeners();
7596
await kill(childProcess);
7697
reject(
7798
new Error('Timed out while waiting for child process to start')

0 commit comments

Comments
 (0)