shout/CLAUDE.md

82 lines
4.1 KiB
Markdown

# 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
- `#` at start of line = comment (not executed, no output expected)
- `$#` also works as comment (legacy syntax)
- `\#` in expected output = literal line starting with `#`
- `#` 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.