done
This commit is contained in:
parent
371fb8c761
commit
84f13946c4
127
SHOUT.md
127
SHOUT.md
|
|
@ -1,127 +0,0 @@
|
||||||
# Shout — Proposed Improvements
|
|
||||||
|
|
||||||
Two additions to the shout test framework: automatic process cleanup and test isolation primitives.
|
|
||||||
|
|
||||||
## 1. Automatic Process Cleanup
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
|
|
||||||
Any test that backgrounds a process (`&`) must manually clean it up. If a test fails or times out before reaching its cleanup command, the process is orphaned and holds its port indefinitely. Every test author has to remember the `trap` pattern:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ my-server &; SVR=$!; trap "kill $SVR 2>/dev/null" EXIT; ...
|
|
||||||
```
|
|
||||||
|
|
||||||
This is error-prone and noisy.
|
|
||||||
|
|
||||||
### Proposal
|
|
||||||
|
|
||||||
Shout already owns the shell process (via `Bun.spawn`). After the shell exits, shout should kill the entire process group to reap any lingering children.
|
|
||||||
|
|
||||||
In `run.ts`, after the shell completes:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Kill the shell's process group to clean up backgrounded children
|
|
||||||
try {
|
|
||||||
process.kill(-proc.pid, "SIGTERM")
|
|
||||||
} catch {}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `-pid` syntax sends the signal to the entire process group. Since shout spawns the shell, the shell and all its children share a process group.
|
|
||||||
|
|
||||||
This requires no syntax changes, no test file modifications, and no action from test authors. Background a process, forget about it — shout cleans up.
|
|
||||||
|
|
||||||
For defense in depth, follow up with SIGKILL after a short grace period:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
try {
|
|
||||||
process.kill(-proc.pid, "SIGTERM")
|
|
||||||
} catch {}
|
|
||||||
setTimeout(() => {
|
|
||||||
try { process.kill(-proc.pid, "SIGKILL") } catch {}
|
|
||||||
}, 500)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migration
|
|
||||||
|
|
||||||
Remove the manual `kill` / `trap` lines from existing test files. They become no-ops but add visual noise.
|
|
||||||
|
|
||||||
## 2. Test Isolation Primitives
|
|
||||||
|
|
||||||
### Problem
|
|
||||||
|
|
||||||
Every test file that needs a server repeats the same boilerplate:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ PORT=19001 ... dev-server > /dev/null 2>&1 & SVR=$!; trap "kill $SVR 2>/dev/null" EXIT; i=0; while ! curl -sf http://localhost:19001/...; do ...; done; echo "ok"
|
|
||||||
ok
|
|
||||||
|
|
||||||
$ mkdir -p .config/dev && echo '{"server":"http://localhost:19001",...}' > .config/dev/config.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Each test picks a hardcoded port. Adding a new test means manually checking which ports are taken. Parallel test runs risk port collisions.
|
|
||||||
|
|
||||||
### Proposal: `# setup` directive
|
|
||||||
|
|
||||||
A new directive that includes commands from a shared file before the test's own commands:
|
|
||||||
|
|
||||||
```
|
|
||||||
# setup tests/setup.shout
|
|
||||||
```
|
|
||||||
|
|
||||||
The setup file is a normal `.shout` file. Its commands are prepended to the test's script (same shell, same working directory, same environment). This is purely textual inclusion — no new execution model.
|
|
||||||
|
|
||||||
Example `tests/setup.shout`:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ dev-server > /dev/null 2>&1 &
|
|
||||||
$ mkdir -p .config/dev && echo "{\"server\":\"http://localhost:$PORT\",\"token\":\"dev-token-1\"}" > .config/dev/config.json
|
|
||||||
$ i=0; while ! curl -sf http://localhost:$PORT/api/whoami -H "Authorization: Bearer dev-token-1" > /dev/null 2>&1; do i=$((i+1)); if [ $i -gt 30 ]; then echo "server failed"; exit 1; fi; sleep 0.2; done; echo "ok"
|
|
||||||
ok
|
|
||||||
```
|
|
||||||
|
|
||||||
Then a test file becomes:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Phase 1 — Linear Timeline
|
|
||||||
# setup tests/setup.shout
|
|
||||||
|
|
||||||
$ dev init myapp
|
|
||||||
initialized repo myapp in ./myapp
|
|
||||||
|
|
||||||
$ cd myapp
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Proposal: `--port-from <N>` flag
|
|
||||||
|
|
||||||
A CLI flag that auto-assigns ports to test files:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
shout --port-from 19000 tests/
|
|
||||||
```
|
|
||||||
|
|
||||||
Shout sets `$PORT` in each test file's environment, incrementing from the base. When `--parallel` is used, each file gets a unique port with no coordination needed.
|
|
||||||
|
|
||||||
Implementation in the runner:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
let nextPort = options.portFrom
|
|
||||||
for (const file of files) {
|
|
||||||
const env = { ...baseEnv, PORT: String(nextPort++) }
|
|
||||||
await runFile(file, { ...options, env })
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Test files reference `$PORT` instead of hardcoded values. Combined with `# setup`, the per-file boilerplate drops to one line.
|
|
||||||
|
|
||||||
### Proposal: `# env` directive
|
|
||||||
|
|
||||||
For cases simpler than `# setup` — setting environment variables without a separate file:
|
|
||||||
|
|
||||||
```
|
|
||||||
# env PORT=19001
|
|
||||||
# env NODE_ENV=production
|
|
||||||
```
|
|
||||||
|
|
||||||
These are injected into the shell environment before any commands run. Lighter than a setup file when all you need is a few variables.
|
|
||||||
Loading…
Reference in New Issue
Block a user