Skip to content

fix(spec): shell completion mocks across bash, zsh, and fish#6

Merged
LeShaunJ merged 6 commits into
fix-relayfrom
claude/fix-shell-completion-tests-llsrK
Apr 25, 2026
Merged

fix(spec): shell completion mocks across bash, zsh, and fish#6
LeShaunJ merged 6 commits into
fix-relayfrom
claude/fix-shell-completion-tests-llsrK

Conversation

@LeShaunJ
Copy link
Copy Markdown
Owner

Fixes the failing shell-completion tests on PR #5 across all three supported shells. Targets fix-relay so PR #5 picks the changes up automatically.

What was broken

  • Bash (apply --file, -e dev "") — bash-completion's _filedir <ext> returns nothing for empty cur because _quote_readline_by_ref "" becomes the literal ''; real bash papers over this with complete -o default. Interactive bash also expanded the ! in _filedir's xspec, and an unset COLUMNS made __cli1_format_comp_descriptions drop descriptions.
  • Zsh (every Linux case, ubuntu CI) — vared -c tmp inside zpty {,}fn aborts with free(): invalid pointer on glibc-based zsh 5.9 builds. The pty's stdout was the abort string, not completions.
  • Zsh (Mac hang) — the rewrite raced zpty -w against ZLE setup in the inner shell, and used mktemp -p (GNU-only). On macOS BSD -p means prefix, so the source file path was wrong.
  • Fish (Mac broken pipe, hidden behind a bare Error:) — fish 3.x read closes stdin after one line when run from -c with a piped stdin (fish-shell#5714). The historical < /dev/stdin workaround opens /dev/fd/0 on macOS and EOFs after one read. The simulator's rescue clause also swallowed the underlying IO::Error when stderr was empty, so the failure surfaced as Error: with no message.

Fixes (in order)

Commit What
c39e93f bash mock: set +H, default COLUMNS=200, wrap _filedir to handle empty cur
89cf657 zsh mock: spawn zsh -i -c 'source <file>' inside zpty so ZLE has its own session; drop the outer EXIT trap (zpty propagates it to the child); tighten the delimiter parser
45bb4a9 zsh mock: portable mktemp + \C-E\n ready handshake so zpty -w waits for bindkey to take effect
5b51c6e shell.cr: surface stderr or the original IO::Error instead of an empty exception message
c698056 fish mock: replace read -l LINE < /dev/stdin with head -n 1 per iteration; works on both Linux and macOS

Test plan

  • crystal spec passes locally on Linux (Crystal 1.15.0, bash 5.2, zsh 5.9, fish 3.7) — 145/145
  • Five consecutive runs of crystal spec spec/mock_spec.cr --tag library all green (no flake)
  • PR author confirmed it passes on M4 macOS
  • CI green on ubuntu-latest + macos-latest across crystal 1.15/1.16/1.17/latest/nightly

Generated by Claude Code

claude added 6 commits April 25, 2026 16:10
The mock never had a real terminal to fall back to, so two latent
bash-completion quirks surfaced as test failures:

- Interactive bash expanded `!` in `_filedir`'s xspec, breaking the
  extension filter for `apply --file ...`.
- An unset `COLUMNS` collapsed `__cli1_format_comp_descriptions` into
  bare names, so `-e dev ""` lost its description columns.

Disable history expansion, default `COLUMNS` to a wide value, and patch
`_filedir` to handle the empty-`cur` case the way real bash does via
`complete -o default`.
`vared -c tmp` inside `zpty {,}myfn` aborts with `free(): invalid
pointer` on glibc-based zsh 5.9 builds (Ubuntu 24.04, recent Homebrew),
so every zsh case in `mock_spec.cr` returned the abort string instead of
completions. Spawning a fresh `zsh -i -c 'source <file>'` inside the
zpty gives ZLE its own interactive session and avoids the heap
corruption. The completion setup is materialized to a tempfile so the
inner shell can pick it up.

Also drop the outer EXIT/signal trap: `zpty` propagates inherited traps
to its child, and after the first iteration that child runs the trap on
exit and deletes the source file the next iteration still needs.
Cleanup now runs once after the loop instead.

Tighten up the delimiter parser too — using `==` substring matches and
keeping the post-D_OPN remainder in `PTY_LINES` makes the unambiguous
single-match path work without a leading blank line.
Two macOS-specific regressions in the new zpty-based mock:

- `mktemp -p /var/tmp …` is GNU-only. BSD `mktemp` (macOS) treats `-p`
  as the prefix flag, so the inner script either pointed at a stale
  path or was empty. Build the path explicitly under TMPDIR (or /tmp)
  and `:>` it instead, which both shells honor.

- `zpty mock 'zsh -fi -c …'` returns immediately, but ZLE inside the
  inner shell isn't ready until later. macOS pty buffering let the
  TAB land before `bindkey` had taken effect, so `vared` sat there
  forever. Have the inner shell print a `\C-E` sentinel right before
  `vared`, and gate `zpty -w` on receiving it. The marker uses a
  trailing newline because the inner shell's stdout is line-buffered.

Also pass `-f` alongside `-i` so the inner shell skips user rcfiles
and starts up faster.
`Exception.new stderr.gets_to_end` produced a bare "Error:" message
when the simulator died without writing to stderr. Reraise with the
shell name and the original IO::Error so the failure has actionable
detail instead of an empty string.
fish 3.x's `read` builtin closes stdin after the first line when run
from a `-c` script with a piped stdin (fish-shell/fish-shell#5714).
The previous `read -l LINE < /dev/stdin` worked around it on Linux but
on macOS BSD `/dev/stdin` opens via /dev/fd/0, which EOFs on the second
iteration and silently killed the loop — every fish case after the
first failed with a broken pipe (and an empty exception message).

Replace the `read` loop with `head -n 1` per iteration. The simulator
is strictly synchronous (one line in, one response out), so `head`'s
read-ahead never eats data the next iteration would need.
Drop the `branches: [main]` filter from `pull_request:` so test runs
also fire for PRs targeting feature branches like `fix-relay`. Tests
should run on every PR; the previous filter silently skipped CI when
PRs were stacked on intermediate branches.
@github-actions
Copy link
Copy Markdown

Test Results

   10 files  ±  0     60 suites  +24   26s ⏱️ +8s
  143 tests ±  0    143 ✅ + 24  0 💤 ±0  0 ❌  -  24 
1 430 runs  +572  1 430 ✅ +711  0 💤 ±0  0 ❌  - 139 

Results for commit af1652a. ± Comparison against base commit 3e876ae.

@LeShaunJ LeShaunJ merged commit d3d3d6d into fix-relay Apr 25, 2026
12 checks passed
@LeShaunJ LeShaunJ deleted the claude/fix-shell-completion-tests-llsrK branch April 25, 2026 17:27
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.

2 participants