Harden the library, and test it under more shells - #4
Merged
Merged
Conversation
Nine ways a build could break, each with an example that fails without the fix: - A chain killed from outside left no status behind, and `run` waited for one for ever. It notices the process is gone and reports the chain as killed instead. - A chain that ignores SIGTERM held cancellation open for ever. It gets GRACE polls and then SIGKILL. - A caller that set -C lost every streamed line after the first poll, and got a complaint a poll instead of output. - A chain took the build script's stdin, on every shell with job control and on none of the ones without. It reads /dev/null now. - mktemp failing left the library writing `1.log` and `1.code` into the working directory. It says so and stops. - A chain command assigning to `n`, `code` or `_work` moved the file the chain reports itself through, which lost the status or hung the run. Everything internal is prefixed now, which also stops the library clobbering names in the calling script. - Grouped output printed the closing marker onto the end of a last line that never got its newline. - `"$@"` with nothing behind it stopped a chain with a label and no commands under posh. - A box drawing character in the source made the file unreadable to a strict shell in the C locale. The sources are ASCII, and `make lint` keeps them that way. The report gained a `killed` line, `GRACE` is a new setting, and the README example called `chain` without the label it needs. Simpler along the way: the chain's status comes from the shell's own exit status rather than a variable it kept alongside; the two reports share one function for the line a chain ends on; a label and a pid are variables rather than files, which also takes a fork per chain per poll out of the wait loop; `&&` chains whose failure would abort a set -e caller are `if` statements. Added to the matrix: posh, bash --posix and zsh --emulate sh on Linux, zsh on macOS. The harness can drive a shell with options now. yash is not there, because shellspec cannot inspect it, though the library does run under it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nine ways a build could break, found by going after them one at a time. Each one has an example that fails without its fix, and the suite went from 24 to 42.
The two that hung a build for ever
A chain killed from outside left no status behind.
runwatches for a status file and nothing was ever going to write one, so a chain that took a SIGKILL — from the OOM killer, from atimeout -s KILL, from anything — left the build waiting until somebody noticed.runnow sees the process is gone, looks for the status once more to settle the race, and reports the chain as killed.A chain that ignores SIGTERM held cancellation open. The wait between asking a chain to go and taking its process group down was unbounded. A chain gets
GRACEpolls, ten seconds at the default, and then the signal nothing can ignore. The bound applies on every shell, not only the ones with process groups: without it the barewaitat the end of cancelling was the thing that hung.The rest
set -Cin the calling script lost the output. The library rewrites one file every poll and noclobber refused, so a build that set it saw the first line and then a complaint a poll on stderr instead of its output./dev/null.mktempfailing was silent. Every path became a bare filename and the library wrote1.log,1.codeand the rest into whatever directory the build ran from. It says so on stderr and stops.$_workand$n, in the chain's own shell, sochain "x" "for n in 1 2 3; ..."wrote the status to the wrong place and hung the run, and a command assigningcodemade a chain that worked report a failure. Everything internal is underscore-prefixed now, which also stops the library clobberingi,n,labeland the rest in the calling script — a caveat the README no longer has to carry.no newline here[!] chain exited 5."$@"with nothing behind it stopped a chain that had a label and no commands under posh.make lintkeeps it that way.Shells
posh,
bash --posixandzsh --emulate shon Linux, and zsh on macOS. posh is Debian's policy-compliant shell and is the one that objected to"$@"; the two POSIX modes are what/bin/shactually is on a lot of machines and differ from their own defaults in ways this library can feel. The harness can drive a shell that carries options now, which is what those two needed.yash is not in the matrix. It found the locale bug and the library does run under it, but shellspec cannot inspect it — yash resolves
[andechothrough PATH, which shellspec's inspection does not survive.Simpler along the way
code, thebreakand the[ -ne 0 ] ||all go.printfs written out in each branch.catper chain per poll out of the wait loop.&&chains whose failure would abort aset -ecaller areifstatements.Also
runreturns 137 for a killed chain and the report gains a[!] label killedline.GRACEis a new setting.chainwith no label says so and returns 2. The README's opening example calledchainwithout the label it needs, and its sample output named a chain the example never declared.Checks
make lintandmake benchpass. The suite passes under dash, bash,bash --posix, ksh93, mksh, zsh,zsh --emulate sh, posh and busybox ash — 42 examples, no failures.Generated by Claude Code