P1.5: drop SuccessExitCodes field and exit-code flexibility
Remove the SuccessExitCodes field from domain.Job and every layer that read or wrote it: runner/exitcodes.go (deleted), runner.go runStateDetail simplified to 0=OK / non-zero=Failed, logfile.go, format.go, operations.go, store.go, job_dialog.go, and jobs_view.go. Tests updated accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,52 +0,0 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
||||
)
|
||||
|
||||
func acceptedExitCode(exitCode int, successExitCodes string) bool {
|
||||
for _, accepted := range parseExitCodes(successExitCodes) {
|
||||
if exitCode == accepted {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parseExitCodes(value string) []int {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return []int{0}
|
||||
}
|
||||
fields := strings.FieldsFunc(value, func(r rune) bool {
|
||||
return r == ',' || r == ';' || r == ' ' || r == '\t' || r == '\n' || r == '\r'
|
||||
})
|
||||
result := make([]int, 0, len(fields))
|
||||
seen := map[int]bool{}
|
||||
for _, field := range fields {
|
||||
code, err := strconv.Atoi(strings.TrimSpace(field))
|
||||
if err != nil || seen[code] {
|
||||
continue
|
||||
}
|
||||
seen[code] = true
|
||||
result = append(result, code)
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return []int{0}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func SuccessExitCodesText(job domain.Job) string {
|
||||
codes := parseExitCodes(job.SuccessExitCodes)
|
||||
parts := make([]string, 0, len(codes))
|
||||
for _, code := range codes {
|
||||
parts = append(parts, strconv.Itoa(code))
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
func successExitCodesText(job domain.Job) string { return SuccessExitCodesText(job) }
|
||||
@@ -23,8 +23,8 @@ func writeRunLog(logsDir string, job domain.Job, trigger string, state string, d
|
||||
// avoid characters that are invalid on Windows or awkward on shells.
|
||||
fileName := started.Format("20060102-150405") + "_" + sanitizeFileName(job.Name) + ".log"
|
||||
path := filepath.Join(logsDir, fileName)
|
||||
content := fmt.Sprintf("time: %s\njob_id: %d\njob_name: %s\ntrigger: %s\nstate: %s\ndetail: %s\ncommand: %s\narguments: %s\nsuccess_exit_codes: %s\nstart_only: %t\n\n%s\n",
|
||||
started.Format("2006-01-02 15:04:05"), job.ID, job.Name, trigger, state, detail, job.Command, logArguments(job.Arguments), successExitCodesText(job), job.StartOnly, output)
|
||||
content := fmt.Sprintf("time: %s\njob_id: %d\njob_name: %s\ntrigger: %s\nstate: %s\ndetail: %s\ncommand: %s\narguments: %s\nstart_only: %t\n\n%s\n",
|
||||
started.Format("2006-01-02 15:04:05"), job.ID, job.Name, trigger, state, detail, job.Command, logArguments(job.Arguments), job.StartOnly, output)
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func RunJob(ctx context.Context, job *domain.Job, trigger string, logsDir string
|
||||
err := command.Run()
|
||||
duration := time.Since(started).Round(time.Millisecond)
|
||||
output = formatOutput(stdoutBuf.String(), stderrBuf.String())
|
||||
state, detail = runStateDetail(err, runCtx.Err(), duration, *job)
|
||||
state, detail = runStateDetail(err, runCtx.Err(), duration)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
@@ -100,7 +100,7 @@ func startOnlyOutput(job domain.Job, pid int) string {
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func runStateDetail(err error, runErr error, duration time.Duration, job domain.Job) (string, string) {
|
||||
func runStateDetail(err error, runErr error, duration time.Duration) (string, string) {
|
||||
if err == nil {
|
||||
return "OK", fmt.Sprintf("Completed in %s (exit code 0)", duration)
|
||||
}
|
||||
@@ -110,14 +110,9 @@ func runStateDetail(err error, runErr error, duration time.Duration, job domain.
|
||||
if errors.Is(err, exec.ErrWaitDelay) {
|
||||
return "OK", fmt.Sprintf("Completed; output capture stopped after %s because a child process kept the stream open", commandWaitDelay)
|
||||
}
|
||||
|
||||
var exitError *exec.ExitError
|
||||
if errors.As(err, &exitError) {
|
||||
exitCode := exitError.ExitCode()
|
||||
if acceptedExitCode(exitCode, job.SuccessExitCodes) {
|
||||
return "OK", fmt.Sprintf("Completed in %s with accepted exit code %d", duration, exitCode)
|
||||
}
|
||||
return "Failed", fmt.Sprintf("Exit code %d is not in success_exit_codes (%s)", exitCode, successExitCodesText(job))
|
||||
return "Failed", fmt.Sprintf("Failed with exit code %d", exitError.ExitCode())
|
||||
}
|
||||
return "Failed", fmt.Sprintf("%T: %v", err, err)
|
||||
}
|
||||
|
||||
@@ -23,10 +23,9 @@ func echoCommand(message string) string {
|
||||
func TestRunJobLogFileAllHeaders(t *testing.T) {
|
||||
logsDir := t.TempDir()
|
||||
job := domain.Job{
|
||||
ID: 99,
|
||||
Name: "Log Header Test",
|
||||
Command: echoCommand("header test output"),
|
||||
SuccessExitCodes: "0,1",
|
||||
ID: 99,
|
||||
Name: "Log Header Test",
|
||||
Command: echoCommand("header test output"),
|
||||
}
|
||||
|
||||
record := RunJob(context.Background(), &job, "Schedule", logsDir)
|
||||
@@ -48,7 +47,6 @@ func TestRunJobLogFileAllHeaders(t *testing.T) {
|
||||
"detail: ",
|
||||
"command: " + job.Command,
|
||||
"arguments: <empty>",
|
||||
"success_exit_codes: 0,1",
|
||||
"start_only: false",
|
||||
"stdout:",
|
||||
"stderr:",
|
||||
@@ -256,40 +254,15 @@ func TestRunJobRunsWindowsCommandWithSeparateArguments(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunJobAcceptsConfiguredExitCode(t *testing.T) {
|
||||
func TestRunJobFailsOnNonZeroExitCode(t *testing.T) {
|
||||
command := `sh -c 'exit 1'`
|
||||
if runtime.GOOS == "windows" {
|
||||
command = `C:\Windows\System32\cmd.exe`
|
||||
}
|
||||
job := domain.Job{
|
||||
ID: 46,
|
||||
Name: "Accepted Exit Code",
|
||||
Command: command,
|
||||
SuccessExitCodes: "0,1",
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
job.Arguments = "/C\nexit /b 1"
|
||||
}
|
||||
|
||||
record := RunJob(context.Background(), &job, "Manual", t.TempDir())
|
||||
if record.State != "OK" {
|
||||
t.Fatalf("expected accepted exit code to be OK, got state %q detail %q", record.State, record.Detail)
|
||||
}
|
||||
if !strings.Contains(record.Detail, "accepted exit code 1") {
|
||||
t.Fatalf("expected accepted exit code detail, got %q", record.Detail)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunJobRejectsUnconfiguredExitCode(t *testing.T) {
|
||||
command := `sh -c 'exit 1'`
|
||||
if runtime.GOOS == "windows" {
|
||||
command = `C:\Windows\System32\cmd.exe`
|
||||
}
|
||||
job := domain.Job{
|
||||
ID: 47,
|
||||
Name: "Rejected Exit Code",
|
||||
Command: command,
|
||||
SuccessExitCodes: "0",
|
||||
ID: 47,
|
||||
Name: "Non-zero Exit Code",
|
||||
Command: command,
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
job.Arguments = "/C\nexit /b 1"
|
||||
@@ -297,9 +270,9 @@ func TestRunJobRejectsUnconfiguredExitCode(t *testing.T) {
|
||||
|
||||
record := RunJob(context.Background(), &job, "Manual", t.TempDir())
|
||||
if record.State != "Failed" {
|
||||
t.Fatalf("expected rejected exit code to fail, got state %q detail %q", record.State, record.Detail)
|
||||
t.Fatalf("expected non-zero exit code to fail, got state %q detail %q", record.State, record.Detail)
|
||||
}
|
||||
if !strings.Contains(record.Detail, "Exit code 1") {
|
||||
if !strings.Contains(record.Detail, "exit code 1") {
|
||||
t.Fatalf("expected exit code detail, got %q", record.Detail)
|
||||
}
|
||||
}
|
||||
@@ -349,19 +322,6 @@ func TestRunJobStartOnlyReportsStartFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseExitCodes(t *testing.T) {
|
||||
got := parseExitCodes("0, 1;2\n3")
|
||||
want := []int{0, 1, 2, 3}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("expected %v, got %v", want, got)
|
||||
}
|
||||
for index := range want {
|
||||
if got[index] != want[index] {
|
||||
t.Fatalf("expected %v, got %v", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirectCommandDoesNotHideWindow(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("Windows window visibility only")
|
||||
|
||||
Reference in New Issue
Block a user