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:
@@ -84,7 +84,7 @@ These land together because both edit `domain/job.go` and `storage/store.go`.
|
|||||||
- [x] P1.2 — `writeJSON` + JSON unmarshal
|
- [x] P1.2 — `writeJSON` + JSON unmarshal
|
||||||
- [x] P1.3 — `gosentry.json` / `jobs.json` paths; drop pysentry name
|
- [x] P1.3 — `gosentry.json` / `jobs.json` paths; drop pysentry name
|
||||||
- [x] P1.4 — One-time YAML import
|
- [x] P1.4 — One-time YAML import
|
||||||
- [ ] P1.5 — Remove `SuccessExitCodes` across code
|
- [x] P1.5 — Remove `SuccessExitCodes` across code
|
||||||
- [ ] P1.6 — Update storage/runner/format tests + TESTS.md
|
- [ ] P1.6 — Update storage/runner/format tests + TESTS.md
|
||||||
|
|
||||||
### Phase 2 — PySentry legacy removal
|
### Phase 2 — PySentry legacy removal
|
||||||
|
|||||||
@@ -50,15 +50,6 @@ func DisplayArguments(arguments string) string {
|
|||||||
return strings.TrimSpace(arguments)
|
return strings.TrimSpace(arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisplaySuccessExitCodes formats a job's success exit codes for display:
|
|
||||||
// "0" (the default) if empty, else the trimmed codes.
|
|
||||||
func DisplaySuccessExitCodes(codes string) string {
|
|
||||||
if strings.TrimSpace(codes) == "" {
|
|
||||||
return "0"
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(codes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisplayRunMode formats a job's execution mode: "Start only" or
|
// DisplayRunMode formats a job's execution mode: "Start only" or
|
||||||
// "Wait for completion".
|
// "Wait for completion".
|
||||||
func DisplayRunMode(job domain.Job) string {
|
func DisplayRunMode(job domain.Job) string {
|
||||||
|
|||||||
@@ -68,15 +68,6 @@ func TestDisplayArguments(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDisplaySuccessExitCodes(t *testing.T) {
|
|
||||||
if got := DisplaySuccessExitCodes(" "); got != "0" {
|
|
||||||
t.Errorf("empty codes = %q, want %q", got, "0")
|
|
||||||
}
|
|
||||||
if got := DisplaySuccessExitCodes(" 0,1 "); got != "0,1" {
|
|
||||||
t.Errorf("codes = %q, want %q", got, "0,1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDisplayRunMode(t *testing.T) {
|
func TestDisplayRunMode(t *testing.T) {
|
||||||
if got := DisplayRunMode(domain.Job{StartOnly: true}); got != "Start only" {
|
if got := DisplayRunMode(domain.Job{StartOnly: true}); got != "Start only" {
|
||||||
t.Errorf("start-only = %q, want %q", got, "Start only")
|
t.Errorf("start-only = %q, want %q", got, "Start only")
|
||||||
|
|||||||
@@ -436,8 +436,6 @@ func runningOutput(job domain.Job, trigger string, started time.Time) string {
|
|||||||
builder.WriteString(job.Command + "\n\n")
|
builder.WriteString(job.Command + "\n\n")
|
||||||
builder.WriteString("arguments:\n")
|
builder.WriteString("arguments:\n")
|
||||||
builder.WriteString(runner.LogArguments(job.Arguments))
|
builder.WriteString(runner.LogArguments(job.Arguments))
|
||||||
builder.WriteString("\n\nsuccess_exit_codes:\n")
|
|
||||||
builder.WriteString(runner.SuccessExitCodesText(job))
|
|
||||||
builder.WriteString("\n\nstart_only:\n")
|
builder.WriteString("\n\nstart_only:\n")
|
||||||
builder.WriteString(fmt.Sprintf("%t", job.StartOnly))
|
builder.WriteString(fmt.Sprintf("%t", job.StartOnly))
|
||||||
return builder.String()
|
return builder.String()
|
||||||
@@ -451,10 +449,6 @@ func normalizeJob(job *domain.Job) {
|
|||||||
job.Schedule = strings.TrimSpace(job.Schedule)
|
job.Schedule = strings.TrimSpace(job.Schedule)
|
||||||
job.Command = strings.TrimSpace(job.Command)
|
job.Command = strings.TrimSpace(job.Command)
|
||||||
job.Arguments = strings.TrimSpace(job.Arguments)
|
job.Arguments = strings.TrimSpace(job.Arguments)
|
||||||
job.SuccessExitCodes = strings.TrimSpace(job.SuccessExitCodes)
|
|
||||||
if job.SuccessExitCodes == "" {
|
|
||||||
job.SuccessExitCodes = "0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateJob enforces the minimum executable definition: name, schedule, and
|
// validateJob enforces the minimum executable definition: name, schedule, and
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ type Job struct {
|
|||||||
Schedule string `json:"schedule"`
|
Schedule string `json:"schedule"`
|
||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
Arguments string `json:"arguments,omitempty"`
|
Arguments string `json:"arguments,omitempty"`
|
||||||
SuccessExitCodes string `json:"success_exit_codes,omitempty"`
|
|
||||||
StartOnly bool `json:"start_only,omitempty"`
|
StartOnly bool `json:"start_only,omitempty"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
// avoid characters that are invalid on Windows or awkward on shells.
|
||||||
fileName := started.Format("20060102-150405") + "_" + sanitizeFileName(job.Name) + ".log"
|
fileName := started.Format("20060102-150405") + "_" + sanitizeFileName(job.Name) + ".log"
|
||||||
path := filepath.Join(logsDir, fileName)
|
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",
|
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), successExitCodesText(job), job.StartOnly, output)
|
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 {
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func RunJob(ctx context.Context, job *domain.Job, trigger string, logsDir string
|
|||||||
err := command.Run()
|
err := command.Run()
|
||||||
duration := time.Since(started).Round(time.Millisecond)
|
duration := time.Since(started).Round(time.Millisecond)
|
||||||
output = formatOutput(stdoutBuf.String(), stderrBuf.String())
|
output = formatOutput(stdoutBuf.String(), stderrBuf.String())
|
||||||
state, detail = runStateDetail(err, runCtx.Err(), duration, *job)
|
state, detail = runStateDetail(err, runCtx.Err(), duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@@ -100,7 +100,7 @@ func startOnlyOutput(job domain.Job, pid int) string {
|
|||||||
return builder.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 {
|
if err == nil {
|
||||||
return "OK", fmt.Sprintf("Completed in %s (exit code 0)", duration)
|
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) {
|
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)
|
return "OK", fmt.Sprintf("Completed; output capture stopped after %s because a child process kept the stream open", commandWaitDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
var exitError *exec.ExitError
|
var exitError *exec.ExitError
|
||||||
if errors.As(err, &exitError) {
|
if errors.As(err, &exitError) {
|
||||||
exitCode := exitError.ExitCode()
|
return "Failed", fmt.Sprintf("Failed with exit code %d", 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("%T: %v", err, err)
|
return "Failed", fmt.Sprintf("%T: %v", err, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ func TestRunJobLogFileAllHeaders(t *testing.T) {
|
|||||||
ID: 99,
|
ID: 99,
|
||||||
Name: "Log Header Test",
|
Name: "Log Header Test",
|
||||||
Command: echoCommand("header test output"),
|
Command: echoCommand("header test output"),
|
||||||
SuccessExitCodes: "0,1",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
record := RunJob(context.Background(), &job, "Schedule", logsDir)
|
record := RunJob(context.Background(), &job, "Schedule", logsDir)
|
||||||
@@ -48,7 +47,6 @@ func TestRunJobLogFileAllHeaders(t *testing.T) {
|
|||||||
"detail: ",
|
"detail: ",
|
||||||
"command: " + job.Command,
|
"command: " + job.Command,
|
||||||
"arguments: <empty>",
|
"arguments: <empty>",
|
||||||
"success_exit_codes: 0,1",
|
|
||||||
"start_only: false",
|
"start_only: false",
|
||||||
"stdout:",
|
"stdout:",
|
||||||
"stderr:",
|
"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'`
|
command := `sh -c 'exit 1'`
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
command = `C:\Windows\System32\cmd.exe`
|
command = `C:\Windows\System32\cmd.exe`
|
||||||
}
|
}
|
||||||
job := domain.Job{
|
job := domain.Job{
|
||||||
ID: 47,
|
ID: 47,
|
||||||
Name: "Rejected Exit Code",
|
Name: "Non-zero Exit Code",
|
||||||
Command: command,
|
Command: command,
|
||||||
SuccessExitCodes: "0",
|
|
||||||
}
|
}
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
job.Arguments = "/C\nexit /b 1"
|
job.Arguments = "/C\nexit /b 1"
|
||||||
@@ -297,9 +270,9 @@ func TestRunJobRejectsUnconfiguredExitCode(t *testing.T) {
|
|||||||
|
|
||||||
record := RunJob(context.Background(), &job, "Manual", t.TempDir())
|
record := RunJob(context.Background(), &job, "Manual", t.TempDir())
|
||||||
if record.State != "Failed" {
|
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)
|
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) {
|
func TestDirectCommandDoesNotHideWindow(t *testing.T) {
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
t.Skip("Windows window visibility only")
|
t.Skip("Windows window visibility only")
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ type yamlJob struct {
|
|||||||
Schedule string `yaml:"schedule"`
|
Schedule string `yaml:"schedule"`
|
||||||
Command string `yaml:"command"`
|
Command string `yaml:"command"`
|
||||||
Arguments string `yaml:"arguments,omitempty"`
|
Arguments string `yaml:"arguments,omitempty"`
|
||||||
SuccessExitCodes string `yaml:"success_exit_codes,omitempty"`
|
|
||||||
StartOnly bool `yaml:"start_only,omitempty"`
|
StartOnly bool `yaml:"start_only,omitempty"`
|
||||||
Enabled bool `yaml:"enabled"`
|
Enabled bool `yaml:"enabled"`
|
||||||
}
|
}
|
||||||
@@ -246,10 +245,6 @@ func normalizeJobs(jobs []domain.Job) {
|
|||||||
job.Command = echoCommand("GoSentry job ran")
|
job.Command = echoCommand("GoSentry job ran")
|
||||||
}
|
}
|
||||||
job.Arguments = strings.TrimSpace(job.Arguments)
|
job.Arguments = strings.TrimSpace(job.Arguments)
|
||||||
job.SuccessExitCodes = strings.TrimSpace(job.SuccessExitCodes)
|
|
||||||
if job.SuccessExitCodes == "" {
|
|
||||||
job.SuccessExitCodes = "0"
|
|
||||||
}
|
|
||||||
// Runtime state (last run, next run, status, output, activity) is no longer
|
// Runtime state (last run, next run, status, output, activity) is no longer
|
||||||
// part of Job. It is reconstructed each time the app starts via
|
// part of Job. It is reconstructed each time the app starts via
|
||||||
// domain.NewRuntime, so normalizeJobs only touches durable configuration.
|
// domain.NewRuntime, so normalizeJobs only touches durable configuration.
|
||||||
|
|||||||
+16
-18
@@ -10,9 +10,17 @@ import (
|
|||||||
"go.yaml.in/yaml/v4"
|
"go.yaml.in/yaml/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func writeYAML(path string, value any) error {
|
||||||
|
data, err := yaml.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, data, 0o644)
|
||||||
|
}
|
||||||
|
|
||||||
func TestJobsRoundTrip(t *testing.T) {
|
func TestJobsRoundTrip(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
path := filepath.Join(dir, "jobs.yaml")
|
path := filepath.Join(dir, "jobs.json")
|
||||||
|
|
||||||
original := []domain.Job{
|
original := []domain.Job{
|
||||||
{
|
{
|
||||||
@@ -22,13 +30,12 @@ func TestJobsRoundTrip(t *testing.T) {
|
|||||||
Schedule: "0 2 * * *",
|
Schedule: "0 2 * * *",
|
||||||
Command: "/usr/bin/backup",
|
Command: "/usr/bin/backup",
|
||||||
Arguments: "--compress\n--verbose",
|
Arguments: "--compress\n--verbose",
|
||||||
SuccessExitCodes: "0,1",
|
|
||||||
StartOnly: true,
|
StartOnly: true,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeYAML(path, domain.JobsFile{Jobs: original}); err != nil {
|
if err := writeJSON(path, domain.JobsFile{Jobs: original}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,9 +66,6 @@ func TestJobsRoundTrip(t *testing.T) {
|
|||||||
if g.Arguments != w.Arguments {
|
if g.Arguments != w.Arguments {
|
||||||
t.Errorf("Arguments: got %q, want %q", g.Arguments, w.Arguments)
|
t.Errorf("Arguments: got %q, want %q", g.Arguments, w.Arguments)
|
||||||
}
|
}
|
||||||
if g.SuccessExitCodes != w.SuccessExitCodes {
|
|
||||||
t.Errorf("SuccessExitCodes: got %q, want %q", g.SuccessExitCodes, w.SuccessExitCodes)
|
|
||||||
}
|
|
||||||
if g.StartOnly != w.StartOnly {
|
if g.StartOnly != w.StartOnly {
|
||||||
t.Errorf("StartOnly: got %v, want %v", g.StartOnly, w.StartOnly)
|
t.Errorf("StartOnly: got %v, want %v", g.StartOnly, w.StartOnly)
|
||||||
}
|
}
|
||||||
@@ -86,10 +90,10 @@ func TestConfigRoundTrip(t *testing.T) {
|
|||||||
MaxLogFiles: 50,
|
MaxLogFiles: 50,
|
||||||
MaxLogAgeDays: 14,
|
MaxLogAgeDays: 14,
|
||||||
StartOnLogin: true,
|
StartOnLogin: true,
|
||||||
KeepRunningInTray: false,
|
KeepRunningInTray: true,
|
||||||
NotifyOnFailure: false,
|
NotifyOnFailure: true,
|
||||||
}
|
}
|
||||||
if err := writeYAML(paths.ConfigPath, want); err != nil {
|
if err := writeJSON(paths.ConfigPath, want); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,12 +129,12 @@ func TestNormalizeJobsFillsDefaults(t *testing.T) {
|
|||||||
jobs := []domain.Job{
|
jobs := []domain.Job{
|
||||||
{Enabled: true},
|
{Enabled: true},
|
||||||
{Enabled: false},
|
{Enabled: false},
|
||||||
{ID: 5, Name: "Kept", Schedule: "*/10 * * * *", SuccessExitCodes: "0,1", Enabled: true},
|
{ID: 5, Name: "Kept", Schedule: "*/10 * * * *", Enabled: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizeJobs(jobs)
|
normalizeJobs(jobs)
|
||||||
|
|
||||||
// Blank enabled job gets default name, schedule, command, and exit codes.
|
// Blank enabled job gets default name, schedule, and command.
|
||||||
// normalizeJobs only fills durable configuration now; runtime status is built
|
// normalizeJobs only fills durable configuration now; runtime status is built
|
||||||
// separately by domain.NewRuntime.
|
// separately by domain.NewRuntime.
|
||||||
if jobs[0].ID != 1 {
|
if jobs[0].ID != 1 {
|
||||||
@@ -142,17 +146,11 @@ func TestNormalizeJobsFillsDefaults(t *testing.T) {
|
|||||||
if jobs[0].Schedule != "@every 1m" {
|
if jobs[0].Schedule != "@every 1m" {
|
||||||
t.Errorf("default schedule: got %q, want '@every 1m'", jobs[0].Schedule)
|
t.Errorf("default schedule: got %q, want '@every 1m'", jobs[0].Schedule)
|
||||||
}
|
}
|
||||||
if jobs[0].SuccessExitCodes != "0" {
|
|
||||||
t.Errorf("default exit codes: got %q, want '0'", jobs[0].SuccessExitCodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-set fields survive normalization unchanged.
|
// Pre-set fields survive normalization unchanged.
|
||||||
if jobs[2].ID != 5 {
|
if jobs[2].ID != 5 {
|
||||||
t.Errorf("pre-set ID should be preserved: got %d, want 5", jobs[2].ID)
|
t.Errorf("pre-set ID should be preserved: got %d, want 5", jobs[2].ID)
|
||||||
}
|
}
|
||||||
if jobs[2].SuccessExitCodes != "0,1" {
|
|
||||||
t.Errorf("pre-set exit codes should be preserved: got %q, want '0,1'", jobs[2].SuccessExitCodes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLoadOrCreateConfigMigratesFromLegacy verifies that when gosentry.json is
|
// TestLoadOrCreateConfigMigratesFromLegacy verifies that when gosentry.json is
|
||||||
@@ -165,7 +163,7 @@ func TestLoadOrCreateConfigMigratesFromLegacy(t *testing.T) {
|
|||||||
ConfigPath: filepath.Join(dir, ConfigFileName), // gosentry.json — not created
|
ConfigPath: filepath.Join(dir, ConfigFileName), // gosentry.json — not created
|
||||||
}
|
}
|
||||||
|
|
||||||
legacy := domain.Config{
|
legacy := yamlConfig{
|
||||||
JobsDir: "/legacy/jobs",
|
JobsDir: "/legacy/jobs",
|
||||||
LogsDir: "/legacy/logs",
|
LogsDir: "/legacy/logs",
|
||||||
MaxLogFiles: 77,
|
MaxLogFiles: 77,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.mixdep.ru/mix/gosentry/src/app"
|
|
||||||
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
@@ -31,9 +30,6 @@ func showJobDialog(w fyne.Window, title string, current job, onSave func(job)) {
|
|||||||
argumentsEntry := widget.NewMultiLineEntry()
|
argumentsEntry := widget.NewMultiLineEntry()
|
||||||
argumentsEntry.SetPlaceHolder(`D:\Local\Jobs\Auto.ffs_batch`)
|
argumentsEntry.SetPlaceHolder(`D:\Local\Jobs\Auto.ffs_batch`)
|
||||||
argumentsEntry.SetText(current.Arguments)
|
argumentsEntry.SetText(current.Arguments)
|
||||||
successExitCodesEntry := widget.NewEntry()
|
|
||||||
successExitCodesEntry.SetPlaceHolder("0")
|
|
||||||
successExitCodesEntry.SetText(app.DisplaySuccessExitCodes(current.SuccessExitCodes))
|
|
||||||
startOnly := widget.NewCheck("Start only, do not wait for exit", nil)
|
startOnly := widget.NewCheck("Start only, do not wait for exit", nil)
|
||||||
startOnly.SetChecked(current.StartOnly)
|
startOnly.SetChecked(current.StartOnly)
|
||||||
enabled := widget.NewCheck("Enabled", nil)
|
enabled := widget.NewCheck("Enabled", nil)
|
||||||
@@ -49,7 +45,6 @@ func showJobDialog(w fyne.Window, title string, current job, onSave func(job)) {
|
|||||||
widget.NewFormItem("Schedule", scheduleEntry),
|
widget.NewFormItem("Schedule", scheduleEntry),
|
||||||
widget.NewFormItem("Command", commandEntry),
|
widget.NewFormItem("Command", commandEntry),
|
||||||
widget.NewFormItem("Arguments", argumentsEntry),
|
widget.NewFormItem("Arguments", argumentsEntry),
|
||||||
widget.NewFormItem("Success exit codes", successExitCodesEntry),
|
|
||||||
widget.NewFormItem("", startOnly),
|
widget.NewFormItem("", startOnly),
|
||||||
widget.NewFormItem("", enabled),
|
widget.NewFormItem("", enabled),
|
||||||
},
|
},
|
||||||
@@ -72,10 +67,6 @@ func showJobDialog(w fyne.Window, title string, current job, onSave func(job)) {
|
|||||||
current.Schedule = strings.TrimSpace(scheduleEntry.Text)
|
current.Schedule = strings.TrimSpace(scheduleEntry.Text)
|
||||||
current.Command = strings.TrimSpace(commandEntry.Text)
|
current.Command = strings.TrimSpace(commandEntry.Text)
|
||||||
current.Arguments = strings.TrimSpace(argumentsEntry.Text)
|
current.Arguments = strings.TrimSpace(argumentsEntry.Text)
|
||||||
current.SuccessExitCodes = strings.TrimSpace(successExitCodesEntry.Text)
|
|
||||||
if current.SuccessExitCodes == "" {
|
|
||||||
current.SuccessExitCodes = "0"
|
|
||||||
}
|
|
||||||
current.StartOnly = startOnly.Checked
|
current.StartOnly = startOnly.Checked
|
||||||
current.Enabled = enabled.Checked
|
current.Enabled = enabled.Checked
|
||||||
// The dialog only edits durable configuration. Runtime status is
|
// The dialog only edits durable configuration. Runtime status is
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ func newJobsView(w fyne.Window, svc *app.Service) (fyne.CanvasObject, func()) {
|
|||||||
scheduleLabel := newJobDetailLabel(jobs[selected].Schedule)
|
scheduleLabel := newJobDetailLabel(jobs[selected].Schedule)
|
||||||
commandLabel := newJobDetailLabel(jobs[selected].Command)
|
commandLabel := newJobDetailLabel(jobs[selected].Command)
|
||||||
argumentsLabel := newJobDetailLabel(jobs[selected].Arguments)
|
argumentsLabel := newJobDetailLabel(jobs[selected].Arguments)
|
||||||
successExitCodesLabel := newJobDetailLabel(app.DisplaySuccessExitCodes(jobs[selected].SuccessExitCodes))
|
|
||||||
runModeLabel := newJobDetailLabel(app.DisplayRunMode(jobs[selected]))
|
runModeLabel := newJobDetailLabel(app.DisplayRunMode(jobs[selected]))
|
||||||
selectedRuntime := runtimeFor(selected)
|
selectedRuntime := runtimeFor(selected)
|
||||||
lastRunLabel := newJobDetailLabel(selectedRuntime.LastRun)
|
lastRunLabel := newJobDetailLabel(selectedRuntime.LastRun)
|
||||||
@@ -93,7 +92,6 @@ func newJobsView(w fyne.Window, svc *app.Service) (fyne.CanvasObject, func()) {
|
|||||||
scheduleLabel.SetText("")
|
scheduleLabel.SetText("")
|
||||||
commandLabel.SetText("")
|
commandLabel.SetText("")
|
||||||
argumentsLabel.SetText("")
|
argumentsLabel.SetText("")
|
||||||
successExitCodesLabel.SetText("")
|
|
||||||
runModeLabel.SetText("")
|
runModeLabel.SetText("")
|
||||||
lastRunLabel.SetText("")
|
lastRunLabel.SetText("")
|
||||||
nextRunLabel.SetText("")
|
nextRunLabel.SetText("")
|
||||||
@@ -110,7 +108,6 @@ func newJobsView(w fyne.Window, svc *app.Service) (fyne.CanvasObject, func()) {
|
|||||||
scheduleLabel.SetText(current.Schedule)
|
scheduleLabel.SetText(current.Schedule)
|
||||||
commandLabel.SetText(current.Command)
|
commandLabel.SetText(current.Command)
|
||||||
argumentsLabel.SetText(app.DisplayArguments(current.Arguments))
|
argumentsLabel.SetText(app.DisplayArguments(current.Arguments))
|
||||||
successExitCodesLabel.SetText(app.DisplaySuccessExitCodes(current.SuccessExitCodes))
|
|
||||||
runModeLabel.SetText(app.DisplayRunMode(current))
|
runModeLabel.SetText(app.DisplayRunMode(current))
|
||||||
lastRunLabel.SetText(rt.LastRun)
|
lastRunLabel.SetText(rt.LastRun)
|
||||||
nextRunLabel.SetText(rt.NextRun)
|
nextRunLabel.SetText(rt.NextRun)
|
||||||
@@ -338,7 +335,6 @@ func newJobsView(w fyne.Window, svc *app.Service) (fyne.CanvasObject, func()) {
|
|||||||
detailRow("Schedule", scheduleLabel),
|
detailRow("Schedule", scheduleLabel),
|
||||||
detailRow("Command", commandLabel),
|
detailRow("Command", commandLabel),
|
||||||
detailRow("Arguments", argumentsLabel),
|
detailRow("Arguments", argumentsLabel),
|
||||||
detailRow("Success exit codes", successExitCodesLabel),
|
|
||||||
detailRow("Run mode", runModeLabel),
|
detailRow("Run mode", runModeLabel),
|
||||||
detailRow("Last run", lastRunLabel),
|
detailRow("Last run", lastRunLabel),
|
||||||
detailRow("Next run", nextRunLabel),
|
detailRow("Next run", nextRunLabel),
|
||||||
|
|||||||
Reference in New Issue
Block a user