shout/CLAUDE.md

4.0 KiB

shout

Transcript-based shell integration test runner. Rust.

Commands

  • cargo test — run tests (integration tests in tests/shout.rs)
  • cargo build — build the binary
  • cargo run -- test [files...] — run shout CLI
    • -u, --update — rewrite .shout files with actual output
    • -k, --keep — keep temp directories after run
    • --clean-env — start with empty environment
    • --path <path> — prepend to $PATH (repeatable)
    • --timeout <dur> — per-command timeout (default 10s)
    • -t, --filter <pattern> — only run files whose path contains <pattern>
    • -v, --verbose — print each command as it runs
    • --port-from <n> — auto-assign $PORT starting from n (default 5400)
    • --parallel — run files in parallel

Architecture

  • src/main.rs — CLI entry point, arg parsing, file discovery, run_one orchestration
  • src/parse.rs — parses .shout files into ShoutFile (list of Command), also parse_setup
  • src/run.rs — executes commands via /bin/sh, captures output with sentinels
  • src/matching.rs — wildcard-aware output matching and diff generation
  • src/format.rs — evaluates pass/fail, formats failures and summary
  • src/update.rs — rewrites .shout files with actual output (--update mode)
  • src/duration.rs — parses duration strings (10s, 500ms, 1m)
  • web/index.html — web documentation page

.shout file format

  • $ prefix = command to execute
  • Lines between commands = expected output (stdout+stderr merged)
  • ... on its own line = multi-line wildcard (matches zero or more lines)
  • ... inline = matches any characters on that line
  • [N] on last line of expected output = assert exit code N
  • [*] = assert any non-zero exit code; default expects 0
  • $# comment line = not executed, no output expected (e.g. $# start the server)
  • # after a command = comment (stripped); # in expected output is literal
  • @env KEY=VALUE before first command = set environment variable
  • @teardown <command> before first command = run command after all test commands
    • Runs regardless of pass/fail
    • Teardown failures produce warnings but don't affect test results
    • Can appear in both .shout files and setup files
  • @setup path.shout before first command = prepend commands (and @env) from another file
    • Setup files use a plain format: each line is a command (no $ prefix), # lines are comments, blank lines ignored
    • Setup files can contain @env, @teardown, and @def directives but not @setup (no nesting)
    • User file @env overrides setup file @env
    • Setup command failures abort the test with an error
  • @def name body before first command = define a macro
    • If a command matches name exactly, body is substituted before execution
    • Backslash \ at end of line continues the body onto the next line
    • Allowed in both .shout files and setup files
    • User file @def overrides setup file @def with the same name
  • Each file runs in a fresh temp dir with a single /bin/sh session
  • $HOME and $SHOUT_DIR are set to the temp dir automatically
  • $SHOUT_SOURCE_DIR is set to the directory containing the .shout file
  • $SHOUT_PROJECT_DIR is set to cwd where shout was invoked
  • stdout and stderr are merged (exec 2>&1)

New feature checklist

  1. src/parse.rs — update types (Directive, ShoutFile, Command) and both parsers (parse + parse_setup)
  2. src/main.rs — wire up the parsed result in run_one (directive resolution, command merging, result handling)
  3. tests/*.shout — integration test file exercising the feature end-to-end
  4. CLAUDE.md — update .shout file format section
  5. README.md — update Directives section
  6. web/index.html — add or update a section on the website
  7. Run cargo test to verify

Style

  • Rust 2024 edition
  • No OOP — plain functions and structs/enums
  • Integration tests in tests/shout.rs, example .shout files in tests/
  • Never use unsafe
  • Keep dependencies to the bare minimum.
  • If you must use a dependency, use the lightest-weight version.