Skip to content

feat(emit): --help, a --panic step, and a gate on the step list - #357

Merged
vyncint merged 2 commits into
mainfrom
feat/emit-help-panic-and-step-drift-gate
Sep 16, 2026
Merged

vyncint merged 2 commits into
mainfrom
feat/emit-help-panic-and-step-drift-gate

Conversation

@vyncint

@vyncint vyncint commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Three issues about the fixture almost every integration test drives.

emit --help (#304)

The step language moves to fixtures/emit/src/steps.txt. The module header
includes it between its ```text fences and --help prints it
after a short banner, so there is one copy and the flag and the header
cannot say different things — which is what #304 asked for, and what makes
#318 possible at all.

$ emit --help
usage: emit [STEP]...

A termlens fixture: writes exactly the bytes its steps describe, in
order, then waits, sleeps, reads or exits as told. Steps apply left
to right.

TEXT             literal text — any argument that is not a step below

$ emit --nonesuch
emit: unknown step `--nonesuch`
emit --help lists every step          # exit 2, unchanged

--help is handled before any step is parsed, so it explains itself
whatever follows it, and it never becomes a Step.

--panic MESSAGE, and what it found (#311)

The README's "What TestBackend cannot see" table and skills/termlens/SKILL.md
§1 both promise a panicking child's message is assertable with
s.contains("panicked"). No fixture had ever panicked, so neither claim had
a test. #311 asked me to write down what I observe, not what I expect,
and to correct the README if the message did not survive.

The README is right. Measured three ways, all now asserted in
crates/termlens/tests/process.rs:

the child what lands on the grid status
panics plainly the message, under what it had drawn exit code 101
panics inside the alternate screen, no hook the message, next to the frame it died on; alternate_screen() still true exit code 101
tears the alternate screen down first (what a hook does) the message on the restored primary screen; what the alt buffer held went with it exit code 101

The status is an exit code of 101 — the Rust runtime's value — and not a
signal, which is the part the issue asked to take from observation rather
than assumption. No README change is needed.

The test uses a 100-column grid on purpose: the runtime's own
panicked at <file>:<line> line wraps on a narrow one, and a wrapped needle
would make the test about the width instead of the panic.

check-emit-steps.sh (#318)

steps.txt and --help are one file, so they cannot drift. What they could
still drift from is the match that parses the steps. The script compares
the documented set against the implemented arms and names the difference in
either direction. It runs in the gates-listed job — no toolchain, just two
greps and a comm — and is listed in CONTRIBUTING §1 with the other scripts.

Proven to fail before being trusted to pass:

$ # a documented step with no arm
::error::`--ghost` is documented in …/steps.txt but no `"--ghost" =>` arm implements it
$ # an arm with no documentation
::error::`--ghost` is implemented in …/main.rs but not documented in …/steps.txt
$ # a rename on one side — both halves reported
::error::`--windowsize` is documented in …/steps.txt but no `"--windowsize" =>` arm implements it
::error::`--winsize` is implemented in …/main.rs but not documented in …/steps.txt

It also refuses to pass when either pattern matches nothing, so a future
edit that breaks the extraction cannot quietly turn the gate into a
tautology — the failure mode a grep-based gate actually has.

On main today: 28 documented, 28 implemented, the same set.

bash 3.2 clean: no declare -A, no GNU-only flags, and the comm output
goes through files rather than a process substitution so the loop's
status=1 is the one that gets read.

Verification

  • cargo test --workspace --all-features green, fmt, clippy … -D warnings,
    RUSTDOCFLAGS='-D warnings' cargo doc --no-deps -p emit clean — the last
    one matters because the module header is now an #![doc = include_str!].
  • check-ci-gates-listed.sh green with the new line in CONTRIBUTING §1.
  • emit --help, an unknown step, and --panic all exercised by hand at a
    prompt before any test was written; the panic behaviour was observed
    through termlens inspect first.

What this does not do

  • No CHANGELOG entry. Nothing here ships: fixtures/emit is
    publish = false and the script is repo infrastructure.
  • zizmor was not run locally — no pipx on this machine. The ci.yml
    change is one - run: with a literal path, no expressions and no new
    permissions, so the zizmor job is the check on it.
  • The thirteen other test files that say "steps are documented in
    fixtures/emit/src/main.rs" are left alone; they still resolve, since the
    header is in main.rs. Only tests/common/mod.rs, the shared helper the
    rest echo, was repointed at steps.txt and the new gate.
  • --help is not itself in steps.txt and not counted by the gate: it is not
    a step, and its arm does not have the shape the script matches. The script
    says so where a reader would otherwise wonder.

Closes #304
Closes #311
Closes #318

`emit` is the fixture almost every integration test drives, and the only
description of its 27 steps was the `//!` header of its main.rs: running it
with a typo printed "see the crate doc in fixtures/emit/src/main.rs".

The step language moves to `fixtures/emit/src/steps.txt`, which the module
header includes between its `text` fences and `--help` prints after a short
banner. One copy, so the two cannot say different things. An unknown step
still exits 2 naming the step.

`--panic MESSAGE` is the 28th step, and it exists to test a claim: the
README's "What TestBackend cannot see" table and SKILL.md §1 both promise a
panicking child's message is assertable with `s.contains("panicked")`, and
nothing in fixtures/ had ever panicked. Measured, three ways, in
process.rs: the message reaches the grid from a plain child, from one that
dies inside the alternate screen (where it joins the frame it died on), and
from one that tore the alternate screen down first (where it lands on the
restored primary screen). The status is exit code 101, not a signal. The
README was right; it now has a test.

Closes #304
Closes #311

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
steps.txt and `emit --help` cannot disagree — they are one file. What they
could still disagree with is the match that parses the steps, and a step
added to one side only is invisible until it costs a test author an
afternoon.

check-emit-steps.sh compares the two sets and names the difference in
either direction. It runs in the gates-listed job because it needs no
toolchain, and is listed in CONTRIBUTING §1 with the other scripts.

Proven to fail three ways before being trusted to pass: a documented step
with no arm, an arm with no documentation, and a rename on one side (which
reports both halves). It also refuses to pass when either pattern matches
nothing, so a future edit that breaks the extraction cannot make the gate
silently vacuous.

Closes #318

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@vyncint
vyncint merged commit b8e70e3 into main Sep 16, 2026
16 checks passed
@vyncint
vyncint deleted the feat/emit-help-panic-and-step-drift-gate branch September 16, 2026 13:10
vyncint added a commit that referenced this pull request Sep 16, 2026
…359)

Two races in tests added on 2026-09-16, both found by stress.yml and
neither reproducible on an idle machine. Test-only.

The panic test (#357) waited for `contains("panicked")` and then asserted
the message. The Rust runtime writes the location line and the message as
separate writes, so the predicate was true one line before the message
existed; the `features` job caught it with the grid holding the location
line and nothing under it. Every wait now names the message — rule 3 of the
wait-semantics contract — and "panicked" is asserted rather than waited on.

`with_stdin` (#356) wrote the input with `.expect(…)`. `diff - -` refuses
before reading anything, so the child can close the pipe first and the
helper panicked on BrokenPipe; stress hit it on both Linux shards. A broken
pipe there is a result, not a failure — the callers assert exit code and
stderr. Proven both ways by forcing the race with a sleep before the write.

Stress on this branch: 15/15 shards green. The Windows queries.rs failure
seen earlier is a pre-existing class and is #360.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant