45 lines
2.7 KiB
Markdown
45 lines
2.7 KiB
Markdown
---
|
|
name: sigterm-kill-test-stub
|
|
description: "Pin \"code kills its child process\" behavior with a bash stub player: make the stub SIGTERM-reactive via sleep&wait, log KILLED via trap; diagnose deferred-trap failures."
|
|
---
|
|
|
|
Use when a test must prove production code sends SIGTERM to a spawned child (player, worker, server stub) and the test asserts the kill happened — or when such a test mysteriously fails with `KILLED` missing and wall time equal to the stub's sleep duration.
|
|
|
|
## The trap-during-sleep gotcha
|
|
|
|
A bash stub that sleeps in the **foreground** does NOT run its TERM trap when signalled: bash defers traps until the current foreground command completes. The stub dies (or logs) only after the sleep finishes — long after the test read the evidence file.
|
|
|
|
Symptoms:
|
|
- Assertion on a `KILLED` marker line fails although production code demonstrably calls `child.kill("SIGTERM")`.
|
|
- Test wall time equals the stub's full sleep (the kill "had no effect").
|
|
|
|
## Fix: reactive stub skeleton
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
printf '%s\n' "$*" >> "$STUB_LOG" # call record
|
|
trap 'printf "KILLED\n" >> "$STUB_LOG"; exit 143' TERM # BEFORE the sleep
|
|
for arg in "$@"; do case "$arg" in *.wav) cp "$arg" "$COPY" ;; esac; done
|
|
sleep "${STUB_HOLD:-0.1}" & # background sleep…
|
|
wait $! # …wait is interruptible
|
|
printf 'DONE\n' >> "$STUB_LOG"
|
|
```
|
|
|
|
- `sleep N & wait $!` is the idiom: a signal makes builtin `wait` return immediately, the trap runs at once, `exit` inside the trap prevents fallthrough to `DONE`.
|
|
- Tune duration per test via the env knob (`STUB_HOLD=5`) so the child is provably still alive when the kill is triggered.
|
|
- Put the trap BEFORE any long command; a trap registered after the sleep never guards it.
|
|
|
|
## Harness wiring (black-box driver pattern)
|
|
|
|
- Scenario: start the long work → settle (>spawn latency, <hold) → trigger the killer (shutdown event, second invocation, `/stop`) → settle → read log.
|
|
- Expose the stub log lines on the driver's stdout (`LOG <line>` per line) so tests grep output, not files: black-box seam stays output-only.
|
|
- Assert `KILLED` present; for interrupt-semantics also count (`kills -eq 1`) and total call lines (`PLAYS 3` = call, KILLED, call).
|
|
|
|
## Orphan hygiene
|
|
|
|
Second child still sleeping when the driver exits: spawn with `stdio: "ignore"` so the orphan holds no capture pipe — it SIGPIPEs/quiets at teardown; do not lengthen tests waiting for it.
|
|
|
|
## Verify before trusting
|
|
|
|
Run the pair separately: kill-tests green while a byte-equality test (`cp` of the payload before sleep) stays green — the reactive rewrite must not drop the copy step (it sits between trap and sleep).
|