CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tap

A Test-Anything-Protocol library for JavaScript with comprehensive testing capabilities and plugin-based architecture

Pending
Overview
Eval results
Files

subprocess-testing.mddocs/

Subprocess Testing

Test external processes, worker threads, and stdin/stdout interactions for comprehensive integration testing.

Capabilities

Process Spawning

Test external commands and processes with full control over arguments, environment, and stdio.

/**
 * Spawn a subprocess for testing with command and arguments
 * @param cmd - Command to execute
 * @param args - Optional array of command arguments
 * @param options - Optional spawn configuration
 * @param name - Optional test name override
 * @returns Promise that resolves when subprocess test completes
 */
function spawn(cmd: string, args?: string[], options?: SpawnOpts, name?: string): Promise<void>;

/**
 * Spawn a subprocess with just command (arguments parsed from command string)
 * @param cmd - Command with arguments as single string
 * @param options - Optional spawn configuration
 * @param name - Optional test name override
 * @returns Promise that resolves when subprocess test completes
 */
function spawn(cmd: string, options?: SpawnOpts, name?: string): Promise<void>;

Usage Examples:

import { test, spawn } from "tap";

// Test CLI command
test("CLI command execution", async (t) => {
  await spawn("echo", ["hello world"], {
    expect: {
      code: 0,
      stdout: "hello world\n"
    }
  });
});

// Test with options
test("node script execution", async (t) => {
  await spawn("node", ["-e", "console.log('test')"], {
    expect: {
      code: 0,
      stdout: /test/
    },
    timeout: 5000
  });
});

// Test command failure
test("failing command", async (t) => {
  await spawn("node", ["-e", "process.exit(1)"], {
    expect: {
      code: 1
    }
  });
});

Worker Thread Testing

Test Node.js worker threads for parallel processing and isolation.

/**
 * Create and test a worker thread
 * @param file - Path to worker script file
 * @param options - Optional worker configuration
 * @param name - Optional test name override
 * @returns Promise that resolves when worker test completes
 */
function worker(file: string, options?: WorkerOpts, name?: string): Promise<void>;

Usage Examples:

// Test worker thread
test("worker thread execution", async (t) => {
  await worker("./test-worker.js", {
    expect: {
      code: 0,
      stdout: "worker completed\n"
    },
    workerData: { input: "test data" }
  });
});

// Test worker with data exchange
test("worker data exchange", async (t) => {
  await worker("./calculator-worker.js", {
    expect: {
      stdout: "42\n"
    },
    workerData: { operation: "add", a: 20, b: 22 }
  });
});

Stdin Testing

Test programs that read from standard input.

/**
 * Test a program with stdin input
 * @param input - String or buffer to send to stdin
 * @param options - Optional stdin test configuration
 * @param name - Optional test name override
 * @returns Promise that resolves when stdin test completes
 */
function stdin(input: string | Buffer, options?: StdinOpts, name?: string): Promise<void>;

/**
 * Test with stdin only (no command specified, tests current process)
 * @param input - String or buffer to send to stdin
 * @param options - Optional configuration
 * @param name - Optional test name override
 * @returns Promise that resolves when test completes
 */
function stdinOnly(input: string | Buffer, options?: StdinOpts, name?: string): Promise<void>;

Usage Examples:

// Test program that reads stdin
test("stdin processing", async (t) => {
  await spawn("node", ["./stdin-processor.js"], {
    stdin: "hello\nworld\n",
    expect: {
      stdout: "HELLO\nWORLD\n"
    }
  });
});

// Test with stdin helper
test("direct stdin test", async (t) => {
  await stdin("test input\n", {
    command: ["node", "./echo-server.js"],
    expect: {
      stdout: "received: test input"
    }
  });
});

Process Options and Configuration

Comprehensive options for controlling subprocess behavior.

interface SpawnOpts {
  /** Expected exit conditions */
  expect?: {
    /** Expected exit code */
    code?: number;
    /** Expected stdout content (string or regex) */
    stdout?: string | RegExp;
    /** Expected stderr content (string or regex) */
    stderr?: string | RegExp;
    /** Signal that should terminate the process */
    signal?: string;
  };
  
  /** Timeout in milliseconds */
  timeout?: number;
  
  /** Environment variables */
  env?: { [key: string]: string };
  
  /** Working directory */
  cwd?: string;
  
  /** Input to send to stdin */
  stdin?: string | Buffer;
  
  /** Encoding for stdout/stderr */
  encoding?: string;
  
  /** Whether to capture stdout */
  stdout?: boolean;
  
  /** Whether to capture stderr */
  stderr?: boolean;
}

interface WorkerOpts extends SpawnOpts {
  /** Data to pass to worker thread */
  workerData?: any;
  
  /** Resource limits for worker */
  resourceLimits?: {
    maxOldGenerationSizeMb?: number;
    maxYoungGenerationSizeMb?: number;
    codeRangeSizeMb?: number;
  };
}

interface StdinOpts extends SpawnOpts {
  /** Command to run (if not using stdinOnly) */
  command?: string[];
}

Advanced Subprocess Testing

Complex testing scenarios with multiple processes and interactions.

// Test process communication
test("process pipe communication", async (t) => {
  // Test producer -> consumer pipeline
  await spawn("echo", ["data"], {
    pipe: {
      to: ["node", "./data-processor.js"]
    },
    expect: {
      code: 0,
      stdout: "processed: data"
    }
  });
});

// Test with environment variables
test("environment-dependent process", async (t) => {
  await spawn("node", ["-e", "console.log(process.env.TEST_VAR)"], {
    env: {
      TEST_VAR: "test-value"
    },
    expect: {
      stdout: "test-value\n"
    }
  });
});

// Test long-running process
test("long-running process", async (t) => {
  await spawn("node", ["-e", `
    setTimeout(() => {
      console.log("completed");
      process.exit(0);
    }, 1000);
  `], {
    timeout: 2000,
    expect: {
      code: 0,
      stdout: "completed\n"
    }
  });
});

// Test process with multiple outputs
test("process with stderr and stdout", async (t) => {
  await spawn("node", ["-e", `
    console.log("stdout message");
    console.error("stderr message");
  `], {
    expect: {
      code: 0,
      stdout: "stdout message\n",
      stderr: "stderr message\n"
    }
  });
});

Interactive Process Testing

Test processes that require interaction or respond to signals.

// Test interactive process
test("interactive process", async (t) => {
  const child = spawn("node", ["./interactive-cli.js"], {
    stdio: "pipe",
    timeout: 5000
  });
  
  // Send input over time
  child.stdin.write("command1\n");
  
  setTimeout(() => {
    child.stdin.write("command2\n");
  }, 100);
  
  setTimeout(() => {
    child.stdin.write("exit\n");
  }, 200);
  
  await child;
  
  t.match(child.stdout, /response1/);
  t.match(child.stdout, /response2/);
});

// Test signal handling
test("process signal handling", async (t) => {
  await spawn("node", ["-e", `
    process.on('SIGTERM', () => {
      console.log('graceful shutdown');
      process.exit(0);
    });
    setTimeout(() => {}, 10000); // Keep alive
  `], {
    signal: "SIGTERM",
    timeout: 1000,
    expect: {
      stdout: "graceful shutdown\n"
    }
  });
});

Testing CLI Applications

Comprehensive patterns for testing command-line applications.

// Test CLI with various arguments
test("CLI argument parsing", async (t) => {
  // Test help flag
  await spawn("./my-cli.js", ["--help"], {
    expect: {
      code: 0,
      stdout: /Usage:/
    }
  });
  
  // Test version flag
  await spawn("./my-cli.js", ["--version"], {
    expect: {
      code: 0,
      stdout: /\d+\.\d+\.\d+/
    }
  });
  
  // Test invalid argument
  await spawn("./my-cli.js", ["--invalid"], {
    expect: {
      code: 1,
      stderr: /Unknown option/
    }
  });
});

// Test CLI with file processing
test("CLI file processing", async (t) => {
  // Create test input file
  const inputFile = "./test-input.txt";
  const outputFile = "./test-output.txt";
  
  await spawn("./file-processor.js", [inputFile, outputFile], {
    expect: {
      code: 0,
      stdout: /Successfully processed/
    }
  });
  
  // Verify output file was created
  const fs = require("fs");
  t.ok(fs.existsSync(outputFile), "output file created");
});

// Test CLI with configuration
test("CLI with config file", async (t) => {
  await spawn("./my-cli.js", ["--config", "./test-config.json"], {
    expect: {
      code: 0,
      stdout: /Configuration loaded/
    }
  });
});

Error Handling and Edge Cases

Handle various error conditions and edge cases in subprocess testing.

// Test command not found
test("command not found", async (t) => {
  await spawn("nonexistent-command", {
    expect: {
      code: 127  // Command not found
    }
  });
});

// Test timeout handling
test("process timeout", async (t) => {
  try {
    await spawn("sleep", ["10"], {
      timeout: 1000  // 1 second timeout
    });
    t.fail("Should have timed out");
  } catch (error) {
    t.match(error.message, /timeout/);
  }
});

// Test memory/resource limits
test("worker resource limits", async (t) => {
  await worker("./memory-intensive-worker.js", {
    resourceLimits: {
      maxOldGenerationSizeMb: 100
    },
    expect: {
      code: 0
    }
  });
});

Subprocess Test Utilities

Helper patterns for common subprocess testing scenarios.

// Utility for testing multiple commands
function testCliCommands(commands: Array<{cmd: string, args: string[], expect: any}>) {
  return Promise.all(commands.map(({cmd, args, expect}) => 
    spawn(cmd, args, { expect })
  ));
}

test("batch CLI testing", async (t) => {
  await testCliCommands([
    { cmd: "echo", args: ["test1"], expect: { stdout: "test1\n" } },
    { cmd: "echo", args: ["test2"], expect: { stdout: "test2\n" } },
    { cmd: "echo", args: ["test3"], expect: { stdout: "test3\n" } }
  ]);
});

Install with Tessl CLI

npx tessl i tessl/npm-tap

docs

assertions.md

index.md

lifecycle-hooks.md

mocking.md

subprocess-testing.md

test-organization.md

tile.json