Fix stdin deadlock and exit code rewrite logic

This commit is contained in:
Chris Wanstrath 2026-03-10 13:14:19 -07:00
parent 909609f21f
commit 3730d8995c
3 changed files with 52 additions and 10 deletions

9
run.go
View File

@ -174,10 +174,8 @@ func runFile(file ShoutFile, opts RunOptions) FileResult {
}
}
_, _ = io.WriteString(stdin, script)
stdin.Close()
// Read stdout with timeout
// Read stdout with timeout — must start reader BEFORE writing to stdin
// to avoid deadlock when pipe buffers fill in both directions.
totalTimeout := opts.Timeout * time.Duration(len(file.Commands))
type readResult struct {
data []byte
@ -189,6 +187,9 @@ func runFile(file ShoutFile, opts RunOptions) FileResult {
ch <- readResult{data, err}
}()
_, _ = io.WriteString(stdin, script)
stdin.Close()
var output []byte
select {
case r := <-ch:

BIN
shout

Binary file not shown.

View File

@ -53,16 +53,45 @@ func rewriteFile(file ShoutFile, results []CommandResult, originalContent string
}
}
// If wildcards match, keep original
if matchOutput(cmd.Expected, result.Actual) {
outputMatch := matchOutput(cmd.Expected, result.Actual)
ecMatch := exitCodeOK(cmd.ExitCodeType, cmd.ExitCodeValue, result.ExitCode)
if outputMatch && ecMatch {
// Both match — preserve original verbatim
output = append(output, oldExpectedRaw...)
} else if outputMatch {
// Output matches but exit code changed — preserve output, fix exit code
raw := oldExpectedRaw
rawTrailing := 0
for k := len(raw) - 1; k >= 0; k-- {
if raw[k] == "" {
rawTrailing++
} else {
break
}
}
content := raw[:len(raw)-rawTrailing]
// Strip old exit code marker if present
if len(content) > 0 && exitCodeMarkerRe.MatchString(content[len(content)-1]) {
content = content[:len(content)-1]
}
output = append(output, content...)
marker := updateExitCodeMarker(cmd, result.ExitCode)
if marker != "" {
output = append(output, marker)
} else if len(content) > 0 && exitCodeMarkerRe.MatchString(content[len(content)-1]) {
output = append(output, "[0]")
}
for k := 0; k < rawTrailing; k++ {
output = append(output, "")
}
} else {
// Output doesn't match — rewrite everything
output = append(output, result.Actual...)
if result.ExitCode != 0 {
output = append(output, fmt.Sprintf("[%d]", result.ExitCode))
marker := updateExitCodeMarker(cmd, result.ExitCode)
if marker != "" {
output = append(output, marker)
} else if len(result.Actual) > 0 && exitCodeMarkerRe.MatchString(result.Actual[len(result.Actual)-1]) {
// Actual output's last line looks like an exit code marker.
// Add an explicit [0] to prevent parser from consuming it.
output = append(output, "[0]")
}
for k := 0; k < trailingBlanks; k++ {
@ -79,3 +108,15 @@ func rewriteFile(file ShoutFile, results []CommandResult, originalContent string
return strings.Join(output, "\n")
}
// updateExitCodeMarker returns the exit code marker to write, preserving [*]
// wildcards when the command still exits non-zero.
func updateExitCodeMarker(cmd Command, actualExitCode int) string {
if cmd.ExitCodeType == ExitCodeWildcard && actualExitCode != 0 {
return "[*]"
}
if actualExitCode != 0 {
return fmt.Sprintf("[%d]", actualExitCode)
}
return ""
}