Skip to content

fix: shape fidelity for replay (timedOut, failed derivation, subprocess-API stubs) - #127

Merged
slgoodrich merged 23 commits into
mainfrom
fix/replay-shape-fidelity
May 3, 2026
Merged

slgoodrich merged 23 commits into
mainfrom
fix/replay-shape-fidelity

Conversation

@slgoodrich

Copy link
Copy Markdown
Owner

What Changed

  • Three replay-fidelity bug fixes. timedOut is now captured and surfaced. Aborted, signal-killed, and maxBuffer-exceeded calls no longer replay as success — synth resolves failed via fallback derivation (exitCode !== 0 || signal !== null || aborted) and the reject branch keys on the resolved value. result.kill(), result.pipe(), and for await (line of result) on execa replay no longer raise TypeError.
  • Three new flag captures for execa: isMaxBuffer, isForcefullyTerminated, isGracefullyCanceled. All five new optional flag fields added to the Result schema (additive; cassette schema stays at version 2).
  • Synth completeness. Replay populates every documented execa Result boolean (isTerminated, isMaxBuffer, isForcefullyTerminated, isGracefullyCanceled, plus empty pipedFrom/ipcOutput) so user assertions reach reachable values.
  • Subprocess-API stubs on execa replay. kill() (no-op returning false), pipe() (throws UnsupportedOptionError), Symbol.asyncIterator (throws UnsupportedOptionError). Mirrors tinyexec's existing pattern.
  • Tests. 7 unit tests (capture + synthesize), 4 integration tests, 2 error-path tests, 2 e2e tests.
  • Docs. docs/execa.md Replay-fidelity section expanded with the full flag table and stubs subsection. docs/tinyexec.md adds a "Failed flag" section explaining the derivation. docs/troubleshooting.md adds an execa-side counterpart to the existing tinyexec replay-method section. CHANGELOG [Unreleased] filled in.

Why

Replay shape now matches real execa/tinyexec behavior on failure paths. Tests asserting on result.timedOut === true, result.isMaxBuffer === true, or result.isForcefullyTerminated === true after a recorded failure now work as expected. Tests that aborted a call no longer silently pass under replay because the cassette is treated as a success. Cassettes recorded before the new fields existed auto-upgrade their replay correctness without re-recording — the fallback derivation handles signal-kill and aborted cases the old exitCode !== 0 check missed.

Related Issues

Refs #126.

Test Plan

  • npm run lint — clean
  • npm run typecheck — clean
  • npm test — 762 passed, 2 skipped (POSIX-only test guards)
  • npm run build — clean

Notes

  • Tinyexec's record path drops aborted/killed from the awaited Output (those getters live on the pre-await ExecProcess). Cassettes recorded by tinyexec currently store aborted: false regardless of whether the call was canceled. Replay-side handling is correct. Tracked in tinyexec adapter: aborted/killed not captured on record path #126; the wrapper restructure is out of scope for this PR.
  • _captureResultForTesting is exported from src/execa.ts and src/tinyexec.ts (both public package entry points) to let unit tests exercise the pure capture function without spawning a subprocess. The underscore prefix marks the symbol as non-public by convention. A follow-up could move it behind a non-exported test-helpers module if the autocomplete leakage becomes a concern.
  • Stream methods (iterable(), readable(), writable(), duplex()) and stream-property getters on execa replay are not stubbed; calls produce TypeError. Tests using these patterns must run with SHELL_CASSETTE_MODE=passthrough.

slgoodrich added 22 commits May 3, 2026 00:19
Tinyexec aborted-call coverage waits on the pre-await capture fix
tracked in #126.
- Hoist subprocess-API stubs and frozen empty arrays to module scope so
  synth no longer allocates them per call.
- Extract a single isTerminated const reusing the signal-check.
- Tighten the comment blocks in synth to keep only the WHY.
- Move SLEEP_5S to tests/helpers/subprocess-targets.ts and share across
  4 test files.
- Extract the replayExeca helper used by 3 unit-test files.

Refs #128 (test export leakage) and #129 (killed derivation) for
follow-ups discovered during this pass.
@slgoodrich slgoodrich closed this May 3, 2026
@slgoodrich slgoodrich reopened this May 3, 2026
@slgoodrich
slgoodrich merged commit 07f3985 into main May 3, 2026
5 of 8 checks passed
@slgoodrich
slgoodrich deleted the fix/replay-shape-fidelity branch May 3, 2026 18:37
@slgoodrich slgoodrich mentioned this pull request May 3, 2026
4 tasks done
slgoodrich added a commit that referenced this pull request May 3, 2026
Patch release covering two bug-fix PRs since v0.6.0:

- #127 (replay shape fidelity): timedOut, failed-derivation correctness,
  and execa subprocess-API stubs
- #130 (record-side flag capture): killed/aborted captured correctly on
  both adapters; inputFile double-read retired; internal export leakage
  removed

Cassette schema stays at version 2; new Result fields are JSON-additive.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant