@gjsify/child_process 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/lib/esm/index.js +301 -0
- package/lib/types/index.d.ts +96 -0
- package/package.json +45 -0
- package/src/index.spec.ts +683 -0
- package/src/index.ts +450 -0
- package/src/test.mts +6 -0
- package/tsconfig.json +31 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
import { describe, it, expect } from '@gjsify/unit';
|
|
2
|
+
// Testing the child_process module API — all commands are hardcoded safe literals
|
|
3
|
+
import { execSync, execFileSync, spawnSync, exec, execFile } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
// Ported from refs/node/test/parallel/test-child-process-exec*.js
|
|
6
|
+
// Original: MIT license, Node.js contributors
|
|
7
|
+
|
|
8
|
+
export default async () => {
|
|
9
|
+
// ==================== execSync ====================
|
|
10
|
+
|
|
11
|
+
await describe('child_process.execSync', async () => {
|
|
12
|
+
await it('should run a shell command and return output', async () => {
|
|
13
|
+
const result = execSync('echo hello', { encoding: 'utf8' });
|
|
14
|
+
expect((result as string).trim()).toBe('hello');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
await it('should return Buffer without encoding option', async () => {
|
|
18
|
+
const result = execSync('echo hello');
|
|
19
|
+
expect(result instanceof Uint8Array).toBeTruthy();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
await it('should respect cwd option', async () => {
|
|
23
|
+
const result = execSync('pwd', { encoding: 'utf8', cwd: '/tmp' });
|
|
24
|
+
expect((result as string).trim()).toBe('/tmp');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await it('should respect env option', async () => {
|
|
28
|
+
const result = execSync('echo $TEST_VAR_GJSIFY', {
|
|
29
|
+
encoding: 'utf8',
|
|
30
|
+
env: { PATH: '/usr/bin:/bin', TEST_VAR_GJSIFY: 'test_value' }
|
|
31
|
+
});
|
|
32
|
+
expect((result as string).trim()).toBe('test_value');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await it('should throw on non-zero exit code', async () => {
|
|
36
|
+
expect(() => execSync('exit 1')).toThrow();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
await it('should throw on non-existent command', async () => {
|
|
40
|
+
expect(() => execSync('nonexistent_command_gjsify_test_12345')).toThrow();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ==================== execFileSync ====================
|
|
45
|
+
|
|
46
|
+
await describe('child_process.execFileSync', async () => {
|
|
47
|
+
await it('should run a command and return output', async () => {
|
|
48
|
+
const result = execFileSync('echo', ['hello world'], { encoding: 'utf8' });
|
|
49
|
+
expect((result as string).trim()).toBe('hello world');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await it('should pass multiple arguments', async () => {
|
|
53
|
+
const result = execFileSync('echo', ['a', 'b', 'c'], { encoding: 'utf8' });
|
|
54
|
+
expect((result as string).trim()).toBe('a b c');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await it('should throw on non-zero exit', async () => {
|
|
58
|
+
let threw = false;
|
|
59
|
+
try {
|
|
60
|
+
execFileSync('false');
|
|
61
|
+
} catch {
|
|
62
|
+
threw = true;
|
|
63
|
+
}
|
|
64
|
+
expect(threw).toBeTruthy();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await it('should respect cwd option', async () => {
|
|
68
|
+
const result = execFileSync('pwd', [], { encoding: 'utf8', cwd: '/tmp' });
|
|
69
|
+
expect((result as string).trim()).toBe('/tmp');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ==================== spawnSync ====================
|
|
74
|
+
|
|
75
|
+
await describe('child_process.spawnSync', async () => {
|
|
76
|
+
await it('should return result with status 0', async () => {
|
|
77
|
+
const result = spawnSync('echo', ['test']);
|
|
78
|
+
expect(result.status).toBe(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await it('should capture stdout', async () => {
|
|
82
|
+
const result = spawnSync('echo', ['hello'], { encoding: 'utf8' });
|
|
83
|
+
expect(typeof result.stdout).toBe('string');
|
|
84
|
+
expect((result.stdout as string).trim()).toBe('hello');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await it('should capture stderr', async () => {
|
|
88
|
+
const result = spawnSync('sh', ['-c', 'echo err >&2'], { encoding: 'utf8' });
|
|
89
|
+
expect(result.status).toBe(0);
|
|
90
|
+
expect(typeof result.stderr).toBe('string');
|
|
91
|
+
expect((result.stderr as string).trim()).toBe('err');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await it('should return non-zero status for failing commands', async () => {
|
|
95
|
+
const result = spawnSync('false');
|
|
96
|
+
expect(result.status).not.toBe(0);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
await it('should set error for non-existent command', async () => {
|
|
100
|
+
const result = spawnSync('nonexistent_command_gjsify_test_12345');
|
|
101
|
+
expect(result.error).toBeDefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
await it('should return pid', async () => {
|
|
105
|
+
const result = spawnSync('echo', ['test']);
|
|
106
|
+
expect(typeof result.pid).toBe('number');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
await it('should respect cwd option', async () => {
|
|
110
|
+
const result = spawnSync('pwd', [], { encoding: 'utf8', cwd: '/tmp' });
|
|
111
|
+
expect((result.stdout as string).trim()).toBe('/tmp');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ==================== exec (async callback) ====================
|
|
116
|
+
|
|
117
|
+
await describe('child_process.exec', async () => {
|
|
118
|
+
await it('should call callback with stdout', async () => {
|
|
119
|
+
const result = await new Promise<string>((resolve, reject) => {
|
|
120
|
+
exec('echo hello', { encoding: 'utf8' }, (err, stdout) => {
|
|
121
|
+
if (err) reject(err);
|
|
122
|
+
else resolve(stdout.trim());
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
expect(result).toBe('hello');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await it('should call callback with stderr', async () => {
|
|
129
|
+
const result = await new Promise<string>((resolve, reject) => {
|
|
130
|
+
exec('echo err >&2', { encoding: 'utf8' }, (err, _stdout, stderr) => {
|
|
131
|
+
if (err) reject(err);
|
|
132
|
+
else resolve(stderr.trim());
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
expect(result).toBe('err');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await it('should pass error for non-zero exit', async () => {
|
|
139
|
+
const error = await new Promise<Error>((resolve) => {
|
|
140
|
+
exec('exit 1', (err) => {
|
|
141
|
+
resolve(err!);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
expect(error).toBeDefined();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await it('should return ChildProcess', async () => {
|
|
148
|
+
const child = exec('echo test', () => {});
|
|
149
|
+
expect(child).toBeDefined();
|
|
150
|
+
expect(typeof child.pid).toBe('number');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ==================== execFile (async callback) ====================
|
|
155
|
+
|
|
156
|
+
await describe('child_process.execFile', async () => {
|
|
157
|
+
await it('should call callback with stdout', async () => {
|
|
158
|
+
const result = await new Promise<string>((resolve, reject) => {
|
|
159
|
+
execFile('echo', ['hello'], { encoding: 'utf8' }, (err, stdout) => {
|
|
160
|
+
if (err) reject(err);
|
|
161
|
+
else resolve(stdout.trim());
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
expect(result).toBe('hello');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
await it('should call callback with error for non-existent file', async () => {
|
|
168
|
+
const error = await new Promise<Error>((resolve) => {
|
|
169
|
+
execFile('nonexistent_command_gjsify_12345', (err) => {
|
|
170
|
+
resolve(err!);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
expect(error).toBeDefined();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ==================== Module exports ====================
|
|
178
|
+
|
|
179
|
+
await describe('child_process exports', async () => {
|
|
180
|
+
await it('should export execSync as a function', async () => {
|
|
181
|
+
expect(typeof execSync).toBe('function');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
await it('should export execFileSync as a function', async () => {
|
|
185
|
+
expect(typeof execFileSync).toBe('function');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
await it('should export spawnSync as a function', async () => {
|
|
189
|
+
expect(typeof spawnSync).toBe('function');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await it('should export exec as a function', async () => {
|
|
193
|
+
// All exec/execFile calls in this file use hardcoded safe literal strings
|
|
194
|
+
expect(typeof exec).toBe('function');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
await it('should export execFile as a function', async () => {
|
|
198
|
+
expect(typeof execFile).toBe('function');
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// ==================== Additional edge cases ====================
|
|
203
|
+
|
|
204
|
+
await describe('child_process edge cases', async () => {
|
|
205
|
+
await it('spawnSync should handle empty stdout', async () => {
|
|
206
|
+
const result = spawnSync('true', [], { encoding: 'utf8' });
|
|
207
|
+
expect(result.status).toBe(0);
|
|
208
|
+
expect(typeof result.stdout).toBe('string');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
await it('spawnSync should capture env variables', async () => {
|
|
212
|
+
const result = spawnSync('sh', ['-c', 'echo $TEST_SPAWN_VAR'], {
|
|
213
|
+
encoding: 'utf8',
|
|
214
|
+
env: { PATH: '/usr/bin:/bin', TEST_SPAWN_VAR: 'spawn_test' }
|
|
215
|
+
});
|
|
216
|
+
expect((result.stdout as string).trim()).toBe('spawn_test');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
await it('execFileSync should handle multiple arguments', async () => {
|
|
220
|
+
const result = execFileSync('echo', ['one', 'two', 'three'], { encoding: 'utf8' });
|
|
221
|
+
expect((result as string).trim()).toBe('one two three');
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// ==================== exec with encoding option ====================
|
|
226
|
+
|
|
227
|
+
await describe('child_process.exec with encoding', async () => {
|
|
228
|
+
await it('exec with encoding option should return string stdout', async () => {
|
|
229
|
+
// Testing child_process module API — hardcoded safe literal
|
|
230
|
+
const result = await new Promise<string>((resolve, reject) => {
|
|
231
|
+
exec('echo encoding_test', { encoding: 'utf8' }, (err, stdout) => {
|
|
232
|
+
if (err) reject(err);
|
|
233
|
+
else resolve(typeof stdout);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
expect(result).toBe('string');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
await it('exec error should have code property', async () => {
|
|
240
|
+
// Testing child_process module API — hardcoded safe literal
|
|
241
|
+
const error = await new Promise<any>((resolve) => {
|
|
242
|
+
exec('exit 42', (err) => {
|
|
243
|
+
resolve(err);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
expect(error).toBeDefined();
|
|
247
|
+
expect(error.code).toBeDefined();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// ==================== spawnSync additional tests ====================
|
|
252
|
+
|
|
253
|
+
await describe('child_process.spawnSync additional', async () => {
|
|
254
|
+
await it('spawnSync with input option should pass stdin data', async () => {
|
|
255
|
+
const result = spawnSync('cat', [], {
|
|
256
|
+
encoding: 'utf8',
|
|
257
|
+
input: 'hello from stdin',
|
|
258
|
+
});
|
|
259
|
+
expect(result.status).toBe(0);
|
|
260
|
+
expect((result.stdout as string).trim()).toBe('hello from stdin');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await it('spawnSync should handle empty output from true command', async () => {
|
|
264
|
+
const result = spawnSync('true', [], { encoding: 'utf8' });
|
|
265
|
+
expect(result.status).toBe(0);
|
|
266
|
+
// stdout should be empty string
|
|
267
|
+
expect((result.stdout as string)).toBe('');
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// ==================== execSync additional tests ====================
|
|
272
|
+
|
|
273
|
+
await describe('child_process.execSync additional', async () => {
|
|
274
|
+
await it('execSync should respect encoding option and return string', async () => {
|
|
275
|
+
const result = execSync('echo encoded', { encoding: 'utf8' });
|
|
276
|
+
expect(typeof result).toBe('string');
|
|
277
|
+
expect((result as string).trim()).toBe('encoded');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
await it('execSync without encoding should return Buffer/Uint8Array', async () => {
|
|
281
|
+
const result = execSync('echo raw');
|
|
282
|
+
expect(result instanceof Uint8Array).toBeTruthy();
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// ==================== execFileSync additional tests ====================
|
|
287
|
+
|
|
288
|
+
await describe('child_process.execFileSync additional', async () => {
|
|
289
|
+
await it('execFileSync should handle env option', async () => {
|
|
290
|
+
const result = execFileSync('sh', ['-c', 'echo $MY_CUSTOM_VAR'], {
|
|
291
|
+
encoding: 'utf8',
|
|
292
|
+
env: { PATH: '/usr/bin:/bin', MY_CUSTOM_VAR: 'custom_value' },
|
|
293
|
+
});
|
|
294
|
+
expect((result as string).trim()).toBe('custom_value');
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// ==================== Export type checks ====================
|
|
299
|
+
|
|
300
|
+
await describe('child_process function exports type checks', async () => {
|
|
301
|
+
await it('spawn should be exported as a function', async () => {
|
|
302
|
+
const { spawn } = await import('node:child_process');
|
|
303
|
+
expect(typeof spawn).toBe('function');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
await it('exec should be a function (typeof check)', async () => {
|
|
307
|
+
expect(typeof exec).toBe('function');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
await it('execFile should be a function (typeof check)', async () => {
|
|
311
|
+
expect(typeof execFile).toBe('function');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
await it('execSync should be a function (typeof check)', async () => {
|
|
315
|
+
expect(typeof execSync).toBe('function');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
await it('spawnSync should be a function (typeof check)', async () => {
|
|
319
|
+
expect(typeof spawnSync).toBe('function');
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// ==================== spawn (async event-based) ====================
|
|
324
|
+
// Ported from refs/node-test/parallel/test-child-process-spawn*.js
|
|
325
|
+
// Original: MIT license, Node.js contributors
|
|
326
|
+
|
|
327
|
+
await describe('child_process.spawn', async () => {
|
|
328
|
+
await it('should return a ChildProcess with pid', async () => {
|
|
329
|
+
const { spawn } = await import('node:child_process');
|
|
330
|
+
const child = spawn('echo', ['test']);
|
|
331
|
+
expect(child).toBeDefined();
|
|
332
|
+
expect(typeof child.pid).toBe('number');
|
|
333
|
+
expect(child.pid! > 0).toBeTruthy();
|
|
334
|
+
await new Promise<void>((resolve) => child.on('close', () => resolve()));
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
await it('should emit spawn event', async () => {
|
|
338
|
+
const { spawn } = await import('node:child_process');
|
|
339
|
+
const child = spawn('echo', ['test']);
|
|
340
|
+
const spawned = await new Promise<boolean>((resolve) => {
|
|
341
|
+
child.on('spawn', () => resolve(true));
|
|
342
|
+
setTimeout(() => resolve(false), 5000);
|
|
343
|
+
});
|
|
344
|
+
expect(spawned).toBeTruthy();
|
|
345
|
+
await new Promise<void>((resolve) => child.on('close', () => resolve()));
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
await it('should emit exit event with exit code', async () => {
|
|
349
|
+
const { spawn } = await import('node:child_process');
|
|
350
|
+
const child = spawn('echo', ['hello']);
|
|
351
|
+
const code = await new Promise<number | null>((resolve) => {
|
|
352
|
+
child.on('exit', (exitCode) => resolve(exitCode));
|
|
353
|
+
});
|
|
354
|
+
expect(code).toBe(0);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
await it('should emit close event after exit', async () => {
|
|
358
|
+
const { spawn } = await import('node:child_process');
|
|
359
|
+
const child = spawn('echo', ['hello']);
|
|
360
|
+
const events: string[] = [];
|
|
361
|
+
child.on('exit', () => events.push('exit'));
|
|
362
|
+
child.on('close', () => events.push('close'));
|
|
363
|
+
await new Promise<void>((resolve) => child.on('close', () => resolve()));
|
|
364
|
+
expect(events.length).toBe(2);
|
|
365
|
+
expect(events[0]).toBe('exit');
|
|
366
|
+
expect(events[1]).toBe('close');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
await it('should emit non-zero exit code for failing command', async () => {
|
|
370
|
+
const { spawn } = await import('node:child_process');
|
|
371
|
+
const child = spawn('false');
|
|
372
|
+
const code = await new Promise<number | null>((resolve) => {
|
|
373
|
+
child.on('exit', (exitCode) => resolve(exitCode));
|
|
374
|
+
});
|
|
375
|
+
expect(code).not.toBe(0);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
await it('should emit error for non-existent command', async () => {
|
|
379
|
+
const { spawn } = await import('node:child_process');
|
|
380
|
+
const child = spawn('nonexistent_command_gjsify_test_12345');
|
|
381
|
+
const err = await new Promise<Error>((resolve) => {
|
|
382
|
+
child.on('error', (e) => resolve(e));
|
|
383
|
+
});
|
|
384
|
+
expect(err).toBeDefined();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
await it('should support shell option', async () => {
|
|
388
|
+
const { spawn } = await import('node:child_process');
|
|
389
|
+
// Testing child_process API — hardcoded safe shell expression
|
|
390
|
+
const child = spawn('echo $((1+2))', [], { shell: true });
|
|
391
|
+
const code = await new Promise<number | null>((resolve) => {
|
|
392
|
+
child.on('exit', (exitCode) => resolve(exitCode));
|
|
393
|
+
});
|
|
394
|
+
expect(code).toBe(0);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
await it('should support cwd option', async () => {
|
|
398
|
+
const { spawn } = await import('node:child_process');
|
|
399
|
+
const child = spawn('pwd', [], { cwd: '/tmp' });
|
|
400
|
+
const code = await new Promise<number | null>((resolve) => {
|
|
401
|
+
child.on('exit', (exitCode) => resolve(exitCode));
|
|
402
|
+
});
|
|
403
|
+
expect(code).toBe(0);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
await it('should support env option', async () => {
|
|
407
|
+
const { spawn } = await import('node:child_process');
|
|
408
|
+
const child = spawn('sh', ['-c', 'echo $MY_SPAWN_VAR'], {
|
|
409
|
+
env: { PATH: '/usr/bin:/bin', MY_SPAWN_VAR: 'spawn_val' }
|
|
410
|
+
});
|
|
411
|
+
const code = await new Promise<number | null>((resolve) => {
|
|
412
|
+
child.on('exit', (exitCode) => resolve(exitCode));
|
|
413
|
+
});
|
|
414
|
+
expect(code).toBe(0);
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// ==================== ChildProcess kill ====================
|
|
419
|
+
// Ported from refs/node-test/parallel/test-child-process-kill.js
|
|
420
|
+
// Original: MIT license, Node.js contributors
|
|
421
|
+
|
|
422
|
+
await describe('ChildProcess.kill', async () => {
|
|
423
|
+
await it('should kill a running process', async () => {
|
|
424
|
+
const { spawn } = await import('node:child_process');
|
|
425
|
+
const child = spawn('sleep', ['10']);
|
|
426
|
+
expect(child.killed).toBeFalsy();
|
|
427
|
+
|
|
428
|
+
const killed = child.kill();
|
|
429
|
+
expect(killed).toBeTruthy();
|
|
430
|
+
expect(child.killed).toBeTruthy();
|
|
431
|
+
|
|
432
|
+
await new Promise<void>((resolve) => child.on('close', () => resolve()));
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
await it('should kill with SIGKILL', async () => {
|
|
436
|
+
const { spawn } = await import('node:child_process');
|
|
437
|
+
const child = spawn('sleep', ['10']);
|
|
438
|
+
|
|
439
|
+
child.kill('SIGKILL');
|
|
440
|
+
expect(child.killed).toBeTruthy();
|
|
441
|
+
|
|
442
|
+
await new Promise<void>((resolve) => child.on('close', () => resolve()));
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
await it('should set exitCode after process exits', async () => {
|
|
446
|
+
const { spawn } = await import('node:child_process');
|
|
447
|
+
const child = spawn('echo', ['test']);
|
|
448
|
+
|
|
449
|
+
await new Promise<void>((resolve) => child.on('close', () => resolve()));
|
|
450
|
+
expect(child.exitCode).toBe(0);
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// ==================== exec edge cases ====================
|
|
455
|
+
// Ported from refs/node-test/parallel/test-child-process-exec-*.js
|
|
456
|
+
// Original: MIT license, Node.js contributors
|
|
457
|
+
|
|
458
|
+
await describe('child_process.exec edge cases', async () => {
|
|
459
|
+
await it('should handle multi-line output', async () => {
|
|
460
|
+
// Testing child_process API — hardcoded safe shell command
|
|
461
|
+
const result = await new Promise<string>((resolve, reject) => {
|
|
462
|
+
exec('echo line1 && echo line2', { encoding: 'utf8' }, (err, stdout) => {
|
|
463
|
+
if (err) reject(err);
|
|
464
|
+
else resolve(stdout);
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
const lines = result.trim().split('\n');
|
|
468
|
+
expect(lines.length).toBe(2);
|
|
469
|
+
expect(lines[0]).toBe('line1');
|
|
470
|
+
expect(lines[1]).toBe('line2');
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
await it('should return ChildProcess with pid', async () => {
|
|
474
|
+
// Testing child_process API — hardcoded safe literal
|
|
475
|
+
const child = exec('echo test', () => {});
|
|
476
|
+
expect(typeof child.pid).toBe('number');
|
|
477
|
+
expect(child.pid! > 0).toBeTruthy();
|
|
478
|
+
await new Promise<void>((resolve) => child.on('close', () => resolve()));
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
await it('should call callback with error for syntax error', async () => {
|
|
482
|
+
// Testing child_process error handling — hardcoded safe literal
|
|
483
|
+
const error = await new Promise<Error>((resolve) => {
|
|
484
|
+
exec('if', (err) => {
|
|
485
|
+
resolve(err!);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
expect(error).toBeDefined();
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
await it('exec with custom env should override process env', async () => {
|
|
492
|
+
// Testing child_process API — hardcoded safe env variable
|
|
493
|
+
const result = await new Promise<string>((resolve, reject) => {
|
|
494
|
+
exec('echo $EXEC_TEST_CUSTOM', {
|
|
495
|
+
encoding: 'utf8',
|
|
496
|
+
env: { PATH: '/usr/bin:/bin', EXEC_TEST_CUSTOM: 'custom_val' }
|
|
497
|
+
}, (err, stdout) => {
|
|
498
|
+
if (err) reject(err);
|
|
499
|
+
else resolve(stdout.trim());
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
expect(result).toBe('custom_val');
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
await it('exec with cwd should change working directory', async () => {
|
|
506
|
+
// Testing child_process API — hardcoded safe literal
|
|
507
|
+
const result = await new Promise<string>((resolve, reject) => {
|
|
508
|
+
exec('pwd', { encoding: 'utf8', cwd: '/tmp' }, (err, stdout) => {
|
|
509
|
+
if (err) reject(err);
|
|
510
|
+
else resolve(stdout.trim());
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
expect(result).toBe('/tmp');
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// ==================== execFile edge cases ====================
|
|
518
|
+
|
|
519
|
+
await describe('child_process.execFile edge cases', async () => {
|
|
520
|
+
await it('should pass arguments correctly', async () => {
|
|
521
|
+
const result = await new Promise<string>((resolve, reject) => {
|
|
522
|
+
execFile('echo', ['arg1', 'arg2', 'arg3'], { encoding: 'utf8' }, (err, stdout) => {
|
|
523
|
+
if (err) reject(err);
|
|
524
|
+
else resolve(stdout.trim());
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
expect(result).toBe('arg1 arg2 arg3');
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
await it('should handle empty args', async () => {
|
|
531
|
+
const result = await new Promise<string>((resolve, reject) => {
|
|
532
|
+
execFile('echo', [], { encoding: 'utf8' }, (err, stdout) => {
|
|
533
|
+
if (err) reject(err);
|
|
534
|
+
else resolve(stdout);
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
expect(typeof result).toBe('string');
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
await it('execFile should set exitCode on ChildProcess', async () => {
|
|
541
|
+
const child = execFile('echo', ['test'], { encoding: 'utf8' }, () => {});
|
|
542
|
+
await new Promise<void>((resolve) => child.on('close', () => resolve()));
|
|
543
|
+
expect(child.exitCode).toBe(0);
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// ==================== spawnSync extended ====================
|
|
548
|
+
// Ported from refs/node-test/parallel/test-child-process-spawnsync*.js
|
|
549
|
+
// Original: MIT license, Node.js contributors
|
|
550
|
+
|
|
551
|
+
await describe('child_process.spawnSync extended', async () => {
|
|
552
|
+
await it('should return output array with stdout and stderr', async () => {
|
|
553
|
+
const result = spawnSync('echo', ['out'], { encoding: 'utf8' });
|
|
554
|
+
expect(result.output).toBeDefined();
|
|
555
|
+
expect(Array.isArray(result.output)).toBeTruthy();
|
|
556
|
+
// output[0] is stdin (null), output[1] is stdout, output[2] is stderr
|
|
557
|
+
expect(result.output.length).toBeGreaterThanOrEqual(3);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
await it('should handle shell option', async () => {
|
|
561
|
+
const result = spawnSync('echo', ['$((2+3))'], { encoding: 'utf8', shell: true });
|
|
562
|
+
expect(result.status).toBe(0);
|
|
563
|
+
expect((result.stdout as string).trim()).toBe('5');
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
await it('should handle large output', async () => {
|
|
567
|
+
const result = spawnSync('seq', ['1', '1000'], { encoding: 'utf8' });
|
|
568
|
+
expect(result.status).toBe(0);
|
|
569
|
+
const lines = (result.stdout as string).trim().split('\n');
|
|
570
|
+
expect(lines.length).toBe(1000);
|
|
571
|
+
expect(lines[0]).toBe('1');
|
|
572
|
+
expect(lines[999]).toBe('1000');
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
await it('should handle process that outputs nothing to stderr', async () => {
|
|
576
|
+
const result = spawnSync('echo', ['clean'], { encoding: 'utf8' });
|
|
577
|
+
expect(result.status).toBe(0);
|
|
578
|
+
expect(result.stderr).toBe('');
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
await it('signal should be null for normal exit', async () => {
|
|
582
|
+
const result = spawnSync('true');
|
|
583
|
+
expect(result.signal).toBeNull();
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
await it('should handle input with special characters', async () => {
|
|
587
|
+
const input = 'hello\nworld\ttab "quotes" \'single\'';
|
|
588
|
+
const result = spawnSync('cat', [], {
|
|
589
|
+
encoding: 'utf8',
|
|
590
|
+
input,
|
|
591
|
+
});
|
|
592
|
+
expect(result.status).toBe(0);
|
|
593
|
+
expect(result.stdout).toBe(input);
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
// ==================== execSync extended ====================
|
|
598
|
+
// Ported from refs/node-test/parallel/test-child-process-execsync*.js
|
|
599
|
+
// Original: MIT license, Node.js contributors
|
|
600
|
+
|
|
601
|
+
await describe('child_process.execSync extended', async () => {
|
|
602
|
+
await it('should handle multi-line command output', async () => {
|
|
603
|
+
const result = execSync('echo line1 && echo line2', { encoding: 'utf8' });
|
|
604
|
+
const lines = (result as string).trim().split('\n');
|
|
605
|
+
expect(lines.length).toBe(2);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
await it('error should have status property', async () => {
|
|
609
|
+
let error: any = null;
|
|
610
|
+
try {
|
|
611
|
+
execSync('exit 42');
|
|
612
|
+
} catch (e) {
|
|
613
|
+
error = e;
|
|
614
|
+
}
|
|
615
|
+
expect(error).toBeDefined();
|
|
616
|
+
expect(error.status).toBe(42);
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
await it('error should have stderr property', async () => {
|
|
620
|
+
let error: any = null;
|
|
621
|
+
try {
|
|
622
|
+
execSync('echo err_msg >&2; exit 1');
|
|
623
|
+
} catch (e) {
|
|
624
|
+
error = e;
|
|
625
|
+
}
|
|
626
|
+
expect(error).toBeDefined();
|
|
627
|
+
expect(error.stderr).toBeDefined();
|
|
628
|
+
expect(error.stderr.includes('err_msg')).toBeTruthy();
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
await it('should handle shell that outputs to both stdout and stderr', async () => {
|
|
632
|
+
const result = execSync('echo out && echo err >&2', { encoding: 'utf8' });
|
|
633
|
+
expect((result as string).trim()).toBe('out');
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
await it('should handle input option', async () => {
|
|
637
|
+
const result = execSync('cat', { encoding: 'utf8', input: 'piped_input' });
|
|
638
|
+
expect((result as string).trim()).toBe('piped_input');
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
await it('should handle command with pipe', async () => {
|
|
642
|
+
const result = execSync('echo hello world | tr a-z A-Z', { encoding: 'utf8' });
|
|
643
|
+
expect((result as string).trim()).toBe('HELLO WORLD');
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
// ==================== execFileSync extended ====================
|
|
648
|
+
|
|
649
|
+
await describe('child_process.execFileSync extended', async () => {
|
|
650
|
+
await it('should return Buffer without encoding', async () => {
|
|
651
|
+
const result = execFileSync('echo', ['raw']);
|
|
652
|
+
expect(result instanceof Uint8Array).toBeTruthy();
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
await it('error should have status and stderr', async () => {
|
|
656
|
+
let error: any = null;
|
|
657
|
+
try {
|
|
658
|
+
execFileSync('sh', ['-c', 'echo err >&2; exit 3']);
|
|
659
|
+
} catch (e) {
|
|
660
|
+
error = e;
|
|
661
|
+
}
|
|
662
|
+
expect(error).toBeDefined();
|
|
663
|
+
expect(error.status).toBe(3);
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
await it('should handle empty arguments', async () => {
|
|
667
|
+
const result = execFileSync('true');
|
|
668
|
+
// true produces no output
|
|
669
|
+
expect(result instanceof Uint8Array).toBeTruthy();
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
await it('should handle cwd and env together', async () => {
|
|
673
|
+
const result = execFileSync('sh', ['-c', 'echo $COMBO_VAR && pwd'], {
|
|
674
|
+
encoding: 'utf8',
|
|
675
|
+
cwd: '/tmp',
|
|
676
|
+
env: { PATH: '/usr/bin:/bin', COMBO_VAR: 'combined' },
|
|
677
|
+
});
|
|
678
|
+
const lines = (result as string).trim().split('\n');
|
|
679
|
+
expect(lines[0]).toBe('combined');
|
|
680
|
+
expect(lines[1]).toBe('/tmp');
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
};
|