or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mdindex.mdlifecycle-hooks.mdmocking.mdsubprocess-testing.mdtest-organization.md

subprocess-testing.mddocs/

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

```