0
# Subprocess Testing
1
2
Test external processes, worker threads, and stdin/stdout interactions for comprehensive integration testing.
3
4
## Capabilities
5
6
### Process Spawning
7
8
Test external commands and processes with full control over arguments, environment, and stdio.
9
10
```typescript { .api }
11
/**
12
* Spawn a subprocess for testing with command and arguments
13
* @param cmd - Command to execute
14
* @param args - Optional array of command arguments
15
* @param options - Optional spawn configuration
16
* @param name - Optional test name override
17
* @returns Promise that resolves when subprocess test completes
18
*/
19
function spawn(cmd: string, args?: string[], options?: SpawnOpts, name?: string): Promise<void>;
20
21
/**
22
* Spawn a subprocess with just command (arguments parsed from command string)
23
* @param cmd - Command with arguments as single string
24
* @param options - Optional spawn configuration
25
* @param name - Optional test name override
26
* @returns Promise that resolves when subprocess test completes
27
*/
28
function spawn(cmd: string, options?: SpawnOpts, name?: string): Promise<void>;
29
```
30
31
**Usage Examples:**
32
33
```typescript
34
import { test, spawn } from "tap";
35
36
// Test CLI command
37
test("CLI command execution", async (t) => {
38
await spawn("echo", ["hello world"], {
39
expect: {
40
code: 0,
41
stdout: "hello world\n"
42
}
43
});
44
});
45
46
// Test with options
47
test("node script execution", async (t) => {
48
await spawn("node", ["-e", "console.log('test')"], {
49
expect: {
50
code: 0,
51
stdout: /test/
52
},
53
timeout: 5000
54
});
55
});
56
57
// Test command failure
58
test("failing command", async (t) => {
59
await spawn("node", ["-e", "process.exit(1)"], {
60
expect: {
61
code: 1
62
}
63
});
64
});
65
```
66
67
### Worker Thread Testing
68
69
Test Node.js worker threads for parallel processing and isolation.
70
71
```typescript { .api }
72
/**
73
* Create and test a worker thread
74
* @param file - Path to worker script file
75
* @param options - Optional worker configuration
76
* @param name - Optional test name override
77
* @returns Promise that resolves when worker test completes
78
*/
79
function worker(file: string, options?: WorkerOpts, name?: string): Promise<void>;
80
```
81
82
**Usage Examples:**
83
84
```typescript
85
// Test worker thread
86
test("worker thread execution", async (t) => {
87
await worker("./test-worker.js", {
88
expect: {
89
code: 0,
90
stdout: "worker completed\n"
91
},
92
workerData: { input: "test data" }
93
});
94
});
95
96
// Test worker with data exchange
97
test("worker data exchange", async (t) => {
98
await worker("./calculator-worker.js", {
99
expect: {
100
stdout: "42\n"
101
},
102
workerData: { operation: "add", a: 20, b: 22 }
103
});
104
});
105
```
106
107
### Stdin Testing
108
109
Test programs that read from standard input.
110
111
```typescript { .api }
112
/**
113
* Test a program with stdin input
114
* @param input - String or buffer to send to stdin
115
* @param options - Optional stdin test configuration
116
* @param name - Optional test name override
117
* @returns Promise that resolves when stdin test completes
118
*/
119
function stdin(input: string | Buffer, options?: StdinOpts, name?: string): Promise<void>;
120
121
/**
122
* Test with stdin only (no command specified, tests current process)
123
* @param input - String or buffer to send to stdin
124
* @param options - Optional configuration
125
* @param name - Optional test name override
126
* @returns Promise that resolves when test completes
127
*/
128
function stdinOnly(input: string | Buffer, options?: StdinOpts, name?: string): Promise<void>;
129
```
130
131
**Usage Examples:**
132
133
```typescript
134
// Test program that reads stdin
135
test("stdin processing", async (t) => {
136
await spawn("node", ["./stdin-processor.js"], {
137
stdin: "hello\nworld\n",
138
expect: {
139
stdout: "HELLO\nWORLD\n"
140
}
141
});
142
});
143
144
// Test with stdin helper
145
test("direct stdin test", async (t) => {
146
await stdin("test input\n", {
147
command: ["node", "./echo-server.js"],
148
expect: {
149
stdout: "received: test input"
150
}
151
});
152
});
153
```
154
155
### Process Options and Configuration
156
157
Comprehensive options for controlling subprocess behavior.
158
159
```typescript { .api }
160
interface SpawnOpts {
161
/** Expected exit conditions */
162
expect?: {
163
/** Expected exit code */
164
code?: number;
165
/** Expected stdout content (string or regex) */
166
stdout?: string | RegExp;
167
/** Expected stderr content (string or regex) */
168
stderr?: string | RegExp;
169
/** Signal that should terminate the process */
170
signal?: string;
171
};
172
173
/** Timeout in milliseconds */
174
timeout?: number;
175
176
/** Environment variables */
177
env?: { [key: string]: string };
178
179
/** Working directory */
180
cwd?: string;
181
182
/** Input to send to stdin */
183
stdin?: string | Buffer;
184
185
/** Encoding for stdout/stderr */
186
encoding?: string;
187
188
/** Whether to capture stdout */
189
stdout?: boolean;
190
191
/** Whether to capture stderr */
192
stderr?: boolean;
193
}
194
195
interface WorkerOpts extends SpawnOpts {
196
/** Data to pass to worker thread */
197
workerData?: any;
198
199
/** Resource limits for worker */
200
resourceLimits?: {
201
maxOldGenerationSizeMb?: number;
202
maxYoungGenerationSizeMb?: number;
203
codeRangeSizeMb?: number;
204
};
205
}
206
207
interface StdinOpts extends SpawnOpts {
208
/** Command to run (if not using stdinOnly) */
209
command?: string[];
210
}
211
```
212
213
### Advanced Subprocess Testing
214
215
Complex testing scenarios with multiple processes and interactions.
216
217
```typescript
218
// Test process communication
219
test("process pipe communication", async (t) => {
220
// Test producer -> consumer pipeline
221
await spawn("echo", ["data"], {
222
pipe: {
223
to: ["node", "./data-processor.js"]
224
},
225
expect: {
226
code: 0,
227
stdout: "processed: data"
228
}
229
});
230
});
231
232
// Test with environment variables
233
test("environment-dependent process", async (t) => {
234
await spawn("node", ["-e", "console.log(process.env.TEST_VAR)"], {
235
env: {
236
TEST_VAR: "test-value"
237
},
238
expect: {
239
stdout: "test-value\n"
240
}
241
});
242
});
243
244
// Test long-running process
245
test("long-running process", async (t) => {
246
await spawn("node", ["-e", `
247
setTimeout(() => {
248
console.log("completed");
249
process.exit(0);
250
}, 1000);
251
`], {
252
timeout: 2000,
253
expect: {
254
code: 0,
255
stdout: "completed\n"
256
}
257
});
258
});
259
260
// Test process with multiple outputs
261
test("process with stderr and stdout", async (t) => {
262
await spawn("node", ["-e", `
263
console.log("stdout message");
264
console.error("stderr message");
265
`], {
266
expect: {
267
code: 0,
268
stdout: "stdout message\n",
269
stderr: "stderr message\n"
270
}
271
});
272
});
273
```
274
275
### Interactive Process Testing
276
277
Test processes that require interaction or respond to signals.
278
279
```typescript
280
// Test interactive process
281
test("interactive process", async (t) => {
282
const child = spawn("node", ["./interactive-cli.js"], {
283
stdio: "pipe",
284
timeout: 5000
285
});
286
287
// Send input over time
288
child.stdin.write("command1\n");
289
290
setTimeout(() => {
291
child.stdin.write("command2\n");
292
}, 100);
293
294
setTimeout(() => {
295
child.stdin.write("exit\n");
296
}, 200);
297
298
await child;
299
300
t.match(child.stdout, /response1/);
301
t.match(child.stdout, /response2/);
302
});
303
304
// Test signal handling
305
test("process signal handling", async (t) => {
306
await spawn("node", ["-e", `
307
process.on('SIGTERM', () => {
308
console.log('graceful shutdown');
309
process.exit(0);
310
});
311
setTimeout(() => {}, 10000); // Keep alive
312
`], {
313
signal: "SIGTERM",
314
timeout: 1000,
315
expect: {
316
stdout: "graceful shutdown\n"
317
}
318
});
319
});
320
```
321
322
### Testing CLI Applications
323
324
Comprehensive patterns for testing command-line applications.
325
326
```typescript
327
// Test CLI with various arguments
328
test("CLI argument parsing", async (t) => {
329
// Test help flag
330
await spawn("./my-cli.js", ["--help"], {
331
expect: {
332
code: 0,
333
stdout: /Usage:/
334
}
335
});
336
337
// Test version flag
338
await spawn("./my-cli.js", ["--version"], {
339
expect: {
340
code: 0,
341
stdout: /\d+\.\d+\.\d+/
342
}
343
});
344
345
// Test invalid argument
346
await spawn("./my-cli.js", ["--invalid"], {
347
expect: {
348
code: 1,
349
stderr: /Unknown option/
350
}
351
});
352
});
353
354
// Test CLI with file processing
355
test("CLI file processing", async (t) => {
356
// Create test input file
357
const inputFile = "./test-input.txt";
358
const outputFile = "./test-output.txt";
359
360
await spawn("./file-processor.js", [inputFile, outputFile], {
361
expect: {
362
code: 0,
363
stdout: /Successfully processed/
364
}
365
});
366
367
// Verify output file was created
368
const fs = require("fs");
369
t.ok(fs.existsSync(outputFile), "output file created");
370
});
371
372
// Test CLI with configuration
373
test("CLI with config file", async (t) => {
374
await spawn("./my-cli.js", ["--config", "./test-config.json"], {
375
expect: {
376
code: 0,
377
stdout: /Configuration loaded/
378
}
379
});
380
});
381
```
382
383
### Error Handling and Edge Cases
384
385
Handle various error conditions and edge cases in subprocess testing.
386
387
```typescript
388
// Test command not found
389
test("command not found", async (t) => {
390
await spawn("nonexistent-command", {
391
expect: {
392
code: 127 // Command not found
393
}
394
});
395
});
396
397
// Test timeout handling
398
test("process timeout", async (t) => {
399
try {
400
await spawn("sleep", ["10"], {
401
timeout: 1000 // 1 second timeout
402
});
403
t.fail("Should have timed out");
404
} catch (error) {
405
t.match(error.message, /timeout/);
406
}
407
});
408
409
// Test memory/resource limits
410
test("worker resource limits", async (t) => {
411
await worker("./memory-intensive-worker.js", {
412
resourceLimits: {
413
maxOldGenerationSizeMb: 100
414
},
415
expect: {
416
code: 0
417
}
418
});
419
});
420
```
421
422
### Subprocess Test Utilities
423
424
Helper patterns for common subprocess testing scenarios.
425
426
```typescript
427
// Utility for testing multiple commands
428
function testCliCommands(commands: Array<{cmd: string, args: string[], expect: any}>) {
429
return Promise.all(commands.map(({cmd, args, expect}) =>
430
spawn(cmd, args, { expect })
431
));
432
}
433
434
test("batch CLI testing", async (t) => {
435
await testCliCommands([
436
{ cmd: "echo", args: ["test1"], expect: { stdout: "test1\n" } },
437
{ cmd: "echo", args: ["test2"], expect: { stdout: "test2\n" } },
438
{ cmd: "echo", args: ["test3"], expect: { stdout: "test3\n" } }
439
]);
440
});
441
```