diff --git a/run.go b/run.go index 8b5f2b1..de24f43 100644 --- a/run.go +++ b/run.go @@ -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: diff --git a/shout b/shout index c7eedaf..10e5c68 100755 Binary files a/shout and b/shout differ diff --git a/update.go b/update.go index 9318445..8efa624 100644 --- a/update.go +++ b/update.go @@ -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 "" +}