Refactor exit code logic and remove Verbose flag
This commit is contained in:
parent
c7730bcaa6
commit
909609f21f
14
cmd.go
14
cmd.go
|
|
@ -44,7 +44,7 @@ func testCmd() *cobra.Command {
|
|||
Short: "Run .shout test files",
|
||||
Args: cobra.ArbitraryArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
timeoutDur, err := parseDuration(timeout)
|
||||
timeoutDur, err := time.ParseDuration(timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -151,7 +151,6 @@ func testCmd() *cobra.Command {
|
|||
PathDirs: pathDirs,
|
||||
EnvVars: envVars,
|
||||
Timeout: timeoutDur,
|
||||
Verbose: verbose,
|
||||
OnCommand: onCommand,
|
||||
})
|
||||
|
||||
|
|
@ -159,16 +158,7 @@ func testCmd() *cobra.Command {
|
|||
for i := 0; i < len(setupCommands) && i < len(fileResult.Results); i++ {
|
||||
r := fileResult.Results[i]
|
||||
sc := setupCommands[i]
|
||||
ok := false
|
||||
switch sc.ExitCodeType {
|
||||
case ExitCodeNone:
|
||||
ok = r.ExitCode == 0
|
||||
case ExitCodeWildcard:
|
||||
ok = r.ExitCode != 0
|
||||
case ExitCodeExact:
|
||||
ok = r.ExitCode == sc.ExitCodeValue
|
||||
}
|
||||
if !ok {
|
||||
if !exitCodeOK(sc.ExitCodeType, sc.ExitCodeValue, r.ExitCode) {
|
||||
if keep {
|
||||
fmt.Fprintln(os.Stderr, fileResult.TmpDir)
|
||||
} else {
|
||||
|
|
|
|||
29
duration.go
29
duration.go
|
|
@ -1,29 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var durationRe = regexp.MustCompile(`^(\d+(?:\.\d+)?)(ms|s|m)$`)
|
||||
|
||||
func parseDuration(s string) (time.Duration, error) {
|
||||
m := durationRe.FindStringSubmatch(s)
|
||||
if m == nil {
|
||||
return 0, fmt.Errorf("invalid duration: %s", s)
|
||||
}
|
||||
|
||||
value, _ := strconv.ParseFloat(m[1], 64)
|
||||
switch m[2] {
|
||||
case "ms":
|
||||
return time.Duration(value * float64(time.Millisecond)), nil
|
||||
case "s":
|
||||
return time.Duration(value * float64(time.Second)), nil
|
||||
case "m":
|
||||
return time.Duration(value * float64(time.Minute)), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown unit: %s", m[2])
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParseDurationMs(t *testing.T) {
|
||||
d, err := parseDuration("500ms")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d != 500*time.Millisecond {
|
||||
t.Errorf("got %v, want 500ms", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDurationSeconds(t *testing.T) {
|
||||
d, err := parseDuration("10s")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d != 10*time.Second {
|
||||
t.Errorf("got %v, want 10s", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDurationDecimal(t *testing.T) {
|
||||
d, err := parseDuration("1.5s")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d != 1500*time.Millisecond {
|
||||
t.Errorf("got %v, want 1.5s", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDurationMinutes(t *testing.T) {
|
||||
d, err := parseDuration("1m")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d != time.Minute {
|
||||
t.Errorf("got %v, want 1m", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDurationInvalid(t *testing.T) {
|
||||
_, err := parseDuration("abc")
|
||||
if err == nil {
|
||||
t.Error("expected error for invalid duration")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDurationNoUnit(t *testing.T) {
|
||||
_, err := parseDuration("10")
|
||||
if err == nil {
|
||||
t.Error("expected error for missing unit")
|
||||
}
|
||||
}
|
||||
19
format.go
19
format.go
|
|
@ -21,16 +21,7 @@ func evaluateFile(path string, results []CommandResult, fileError string) TestRe
|
|||
for _, r := range results {
|
||||
cmd := r.Command
|
||||
outputMatches := matchOutput(cmd.Expected, r.Actual)
|
||||
|
||||
var exitCodeMismatch bool
|
||||
switch cmd.ExitCodeType {
|
||||
case ExitCodeNone:
|
||||
exitCodeMismatch = r.ExitCode != 0
|
||||
case ExitCodeWildcard:
|
||||
exitCodeMismatch = r.ExitCode == 0
|
||||
case ExitCodeExact:
|
||||
exitCodeMismatch = r.ExitCode != cmd.ExitCodeValue
|
||||
}
|
||||
exitCodeMismatch := !exitCodeOK(cmd.ExitCodeType, cmd.ExitCodeValue, r.ExitCode)
|
||||
|
||||
if !outputMatches || exitCodeMismatch {
|
||||
var diffLines []DiffLine
|
||||
|
|
@ -114,7 +105,12 @@ func formatFailure(t TestResult) string {
|
|||
func formatSummary(results []TestResult, elapsed time.Duration) string {
|
||||
totalCommands := 0
|
||||
failedCommands := 0
|
||||
errorCount := 0
|
||||
for _, r := range results {
|
||||
if r.Error != "" {
|
||||
errorCount++
|
||||
continue
|
||||
}
|
||||
totalCommands += r.CommandCount
|
||||
failedCommands += len(r.Failures)
|
||||
}
|
||||
|
|
@ -127,6 +123,9 @@ func formatSummary(results []TestResult, elapsed time.Duration) string {
|
|||
if failedCommands > 0 {
|
||||
parts = append(parts, red(fmt.Sprintf("%d failed", failedCommands)))
|
||||
}
|
||||
if errorCount > 0 {
|
||||
parts = append(parts, red(fmt.Sprintf("%d errored", errorCount)))
|
||||
}
|
||||
|
||||
var timeStr string
|
||||
if elapsed < time.Second {
|
||||
|
|
|
|||
74
run.go
74
run.go
|
|
@ -24,7 +24,6 @@ type RunOptions struct {
|
|||
PathDirs []string
|
||||
EnvVars map[string]string
|
||||
Timeout time.Duration
|
||||
Verbose bool
|
||||
OnCommand func(Command)
|
||||
}
|
||||
|
||||
|
|
@ -39,28 +38,8 @@ func buildScript(commands []Command, sentinelPrefix string) string {
|
|||
return b.String()
|
||||
}
|
||||
|
||||
func parseSentinelOutput(raw string, commandCount int, sentinelPrefix string) (outputs [][]string, exitCodes []int) {
|
||||
re := regexp.MustCompile(regexp.QuoteMeta(sentinelPrefix) + `(\d+)_(\d+)__`)
|
||||
|
||||
remaining := raw
|
||||
for i := 0; i < commandCount; i++ {
|
||||
loc := re.FindStringSubmatchIndex(remaining)
|
||||
if loc == nil {
|
||||
lines := strings.Split(remaining, "\n")
|
||||
if len(lines) > 0 && lines[0] == "" {
|
||||
lines = lines[1:]
|
||||
}
|
||||
lines = trimTrailingEmpty(lines)
|
||||
outputs = append(outputs, lines)
|
||||
exitCodes = append(exitCodes, 1)
|
||||
break
|
||||
}
|
||||
|
||||
before := remaining[:loc[0]]
|
||||
exitCodeStr := remaining[loc[2]:loc[3]]
|
||||
ec, _ := strconv.Atoi(exitCodeStr)
|
||||
|
||||
lines := strings.Split(before, "\n")
|
||||
func splitSentinelBlock(s string) []string {
|
||||
lines := strings.Split(s, "\n")
|
||||
if len(lines) > 0 && lines[0] == "" {
|
||||
lines = lines[1:]
|
||||
}
|
||||
|
|
@ -68,8 +47,39 @@ func parseSentinelOutput(raw string, commandCount int, sentinelPrefix string) (o
|
|||
if len(lines) == 1 && lines[0] == "" {
|
||||
lines = nil
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
outputs = append(outputs, lines)
|
||||
func parseSentinelOutput(raw string, commandCount int, sentinelPrefix string) (outputs [][]string, exitCodes []int) {
|
||||
re := regexp.MustCompile(regexp.QuoteMeta(sentinelPrefix) + `(\d+)_(\d+)__`)
|
||||
|
||||
remaining := raw
|
||||
for i := 0; i < commandCount; i++ {
|
||||
loc := re.FindStringSubmatchIndex(remaining)
|
||||
if loc == nil {
|
||||
// No more sentinels — assign remaining output, mark as failed
|
||||
outputs = append(outputs, splitSentinelBlock(remaining))
|
||||
exitCodes = append(exitCodes, 1)
|
||||
remaining = ""
|
||||
continue
|
||||
}
|
||||
|
||||
exitCodeStr := remaining[loc[2]:loc[3]]
|
||||
ec, _ := strconv.Atoi(exitCodeStr)
|
||||
cmdIdxStr := remaining[loc[4]:loc[5]]
|
||||
cmdIdx, _ := strconv.Atoi(cmdIdxStr)
|
||||
before := remaining[:loc[0]]
|
||||
|
||||
// If sentinel belongs to a later command, this command's sentinel is missing
|
||||
if cmdIdx > i {
|
||||
outputs = append(outputs, splitSentinelBlock(before))
|
||||
exitCodes = append(exitCodes, 1)
|
||||
// Keep remaining from the sentinel onwards for the next iteration
|
||||
remaining = remaining[loc[0]:]
|
||||
continue
|
||||
}
|
||||
|
||||
outputs = append(outputs, splitSentinelBlock(before))
|
||||
exitCodes = append(exitCodes, ec)
|
||||
|
||||
afterSentinel := remaining[loc[1]:]
|
||||
|
|
@ -101,11 +111,8 @@ func runFile(file ShoutFile, opts RunOptions) FileResult {
|
|||
script := buildScript(file.Commands, sentinel)
|
||||
|
||||
// Build environment
|
||||
var envMap map[string]string
|
||||
if opts.CleanEnv {
|
||||
envMap = make(map[string]string)
|
||||
} else {
|
||||
envMap = make(map[string]string)
|
||||
envMap := make(map[string]string)
|
||||
if !opts.CleanEnv {
|
||||
for _, e := range os.Environ() {
|
||||
if k, v, ok := strings.Cut(e, "="); ok {
|
||||
envMap[k] = v
|
||||
|
|
@ -161,6 +168,12 @@ func runFile(file ShoutFile, opts RunOptions) FileResult {
|
|||
}
|
||||
}()
|
||||
|
||||
if opts.OnCommand != nil {
|
||||
for _, c := range file.Commands {
|
||||
opts.OnCommand(c)
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = io.WriteString(stdin, script)
|
||||
stdin.Close()
|
||||
|
||||
|
|
@ -198,9 +211,6 @@ func runFile(file ShoutFile, opts RunOptions) FileResult {
|
|||
|
||||
results := make([]CommandResult, len(file.Commands))
|
||||
for i, c := range file.Commands {
|
||||
if opts.Verbose && opts.OnCommand != nil {
|
||||
opts.OnCommand(c)
|
||||
}
|
||||
actual := outputs[i]
|
||||
if actual == nil {
|
||||
actual = []string{}
|
||||
|
|
|
|||
13
types.go
13
types.go
|
|
@ -9,6 +9,19 @@ const (
|
|||
ExitCodeWildcard // [*] — expect any non-zero
|
||||
)
|
||||
|
||||
func exitCodeOK(ecType ExitCodeType, ecValue int, actual int) bool {
|
||||
switch ecType {
|
||||
case ExitCodeNone:
|
||||
return actual == 0
|
||||
case ExitCodeWildcard:
|
||||
return actual != 0
|
||||
case ExitCodeExact:
|
||||
return actual == ecValue
|
||||
default:
|
||||
return actual == 0
|
||||
}
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
Line int
|
||||
Raw string
|
||||
|
|
|
|||
20
update.go
20
update.go
|
|
@ -43,16 +43,6 @@ func rewriteFile(file ShoutFile, results []CommandResult, originalContent string
|
|||
|
||||
oldExpectedRaw := lines[i+1 : j]
|
||||
|
||||
// Check for exit code marker
|
||||
oldTrimmed := trimTrailingEmpty(oldExpectedRaw)
|
||||
var oldExitMarker string
|
||||
if len(oldTrimmed) > 0 {
|
||||
last := oldTrimmed[len(oldTrimmed)-1]
|
||||
if exitCodeMarkerRe.MatchString(last) {
|
||||
oldExitMarker = last
|
||||
}
|
||||
}
|
||||
|
||||
// Count trailing blank lines
|
||||
trailingBlanks := 0
|
||||
for k := len(oldExpectedRaw) - 1; k >= 0; k-- {
|
||||
|
|
@ -68,12 +58,12 @@ func rewriteFile(file ShoutFile, results []CommandResult, originalContent string
|
|||
output = append(output, oldExpectedRaw...)
|
||||
} else {
|
||||
output = append(output, result.Actual...)
|
||||
if oldExitMarker != "" {
|
||||
output = append(output, oldExitMarker)
|
||||
if result.ExitCode != 0 {
|
||||
output = append(output, fmt.Sprintf("[%d]", result.ExitCode))
|
||||
} 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 marker to prevent parser from consuming it as one.
|
||||
output = append(output, fmt.Sprintf("[%d]", result.ExitCode))
|
||||
// Add an explicit [0] to prevent parser from consuming it.
|
||||
output = append(output, "[0]")
|
||||
}
|
||||
for k := 0; k < trailingBlanks; k++ {
|
||||
output = append(output, "")
|
||||
|
|
@ -82,7 +72,7 @@ func rewriteFile(file ShoutFile, results []CommandResult, originalContent string
|
|||
|
||||
i = j - 1
|
||||
cmdIdx++
|
||||
} else if cmdIdx == 0 {
|
||||
} else if cmdIdx == 0 || cmdIdx >= len(file.Commands) {
|
||||
output = append(output, line)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user