4.9 KB156 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import type {EjecaChildProcess} from 'shared/ejeca';
9
10import {ejeca} from 'shared/ejeca';
11
12describe('test running binaries', () => {
13 it('we can get both streams and awaitables', async () => {
14 const spawned = ejeca('node', ['-e', "console.log('uno') ; console.error('dos')"]);
15 let streamOut = '';
16 let streamErr = '';
17 spawned.stdout?.on('data', data => {
18 streamOut = data.toString();
19 });
20 spawned.stderr?.on('data', data => {
21 streamErr = data.toString();
22 });
23 const result = await spawned;
24 expect(result.stdout).toBe('uno');
25 expect(streamOut).toBe('uno\n');
26 expect(result.stderr).toBe('dos');
27 expect(streamErr).toBe('dos\n');
28 });
29
30 it('we can set pass stdin as a string', async () => {
31 const spawned = ejeca('node', [], {input: 'console.log("hemlo")'});
32 expect((await spawned).stdout).toBe('hemlo');
33 });
34
35 it('when erroring out the command name is present', async () => {
36 const spawned = ejeca('node', ['-', 'foo("bar")'], {input: 'babar'});
37 await expect(spawned).rejects.toThrowErrorMatchingInlineSnapshot(
38 `"Command \`node "-" "foo(\\"bar\\")"\` exited with non-zero status with exit code 1"`,
39 );
40 });
41
42 it('handles env var options correctly', async () => {
43 // We use yarn for our tests, so YARN_IGNORE_PATH should always be set
44 const input =
45 'console.log("YARN_IGNORE_PATH" in process.env ? process.env.YARN_IGNORE_PATH : "not set")';
46 let spawned = ejeca('node', ['-'], {input});
47 expect((await spawned).stdout).not.toBe('babar');
48 spawned = ejeca('node', ['-'], {input, env: {YARN_IGNORE_PATH: 'babar'}});
49 expect((await spawned).stdout).toBe('babar');
50 spawned = ejeca('node', ['-'], {input, env: {YARN_IGNORE_PATH: 'babar'}, extendEnv: true});
51 expect((await spawned).stdout).toBe('babar');
52 spawned = ejeca('node', ['-'], {
53 input,
54 env: {FOO: 'bar', PATH: process.env.PATH},
55 extendEnv: false,
56 });
57 expect((await spawned).stdout).toBe('not set');
58 });
59
60 it('can specify whether to strip the final line', async () => {
61 const input = 'console.log("hello")';
62 let spawned;
63 spawned = ejeca('node', ['-'], {input});
64 expect((await spawned).stdout).toBe('hello');
65 spawned = ejeca('node', ['-'], {input, stripFinalNewline: true});
66 expect((await spawned).stdout).toBe('hello');
67 spawned = ejeca('node', ['-'], {input, stripFinalNewline: false});
68 expect((await spawned).stdout).toBe('hello\n');
69 });
70
71 it('we can specify stdin', async () => {
72 let spawned;
73 spawned = ejeca('node', [], {input: 'console.log("hemlo")'});
74 expect((await spawned).stdout).toBe('hemlo');
75 spawned = ejeca('node', [], {input: 'console.log("hemlo")', stdin: 'pipe'});
76 expect((await spawned).stdout).toBe('hemlo');
77 spawned = ejeca('node', [], {input: 'console.log("hemlo")', stdin: 'ignore'});
78 expect((await spawned).stdout).toBe('');
79 });
80});
81
82describe('test killing process', () => {
83 const sighandlerScript = `
84const argv = process.argv;
85const sleep = (waitTimeInMs) => new Promise(resolve => setTimeout(resolve, waitTimeInMs));
86
87(async function main() {
88let exitOnSigTerm = false;
89let delay = 0;
90
91if(argv.length > 2) {
92 delay = parseInt(argv[2]);
93 if(argv[argv.length - 1] !== "dontExitOnSigterm") {
94 exitOnSigTerm = true;
95 }
96}
97
98process.on('SIGTERM', () => {
99 console.log("I was asked to stop politely");
100 if(exitOnSigTerm) {
101 process.exit(0)
102 }
103});
104
105console.log("Hello");
106
107for(let i=0; i < delay; i++) {
108 await sleep(1000);
109}
110
111console.log("Goodbye");
112})();
113`;
114
115 const spawnAndKill = async (
116 pythonArgs: string[] = [],
117 expectedOut: string = '',
118 killArgs: Parameters<EjecaChildProcess['kill']> = [],
119 expectedSignal?: string,
120 ) => {
121 const spawned = ejeca('node', ['-', ...pythonArgs], {
122 input: sighandlerScript,
123 });
124 setTimeout(() => spawned.kill(...killArgs), 1000);
125 let outo = '';
126 let signalo = undefined;
127 try {
128 outo = (await spawned).stdout;
129 } catch (err) {
130 if (err != null && typeof err === 'object' && 'stdout' in err && 'signal' in err) {
131 outo = err.stdout as string;
132 signalo = err.signal;
133 }
134 }
135 expect(outo).toBe(expectedOut);
136 expect(signalo).toBe(expectedSignal);
137 };
138
139 it('kill as sends sigterm by default', async () => {
140 await spawnAndKill(['3', 'dontExitOnSigterm'], 'Hello\nI was asked to stop politely\nGoodbye');
141 });
142
143 it('sigkill can be set through force kill after a timeout', async () => {
144 await spawnAndKill(
145 ['4', 'dontExitOnSigterm'],
146 'Hello\nI was asked to stop politely',
147 ['SIGTERM', {forceKillAfterTimeout: 2000}],
148 'SIGKILL',
149 );
150 });
151
152 it('sending sigkill just kills', async () => {
153 await spawnAndKill(['100000000000'], 'Hello', ['SIGKILL'], 'SIGKILL');
154 });
155});
156