package cron import ( "testing" "time" ) func TestNextRun(t *testing.T) { loc := time.UTC from := time.Date(2026, 9, 17, 15, 30, 0, 0, loc) // Thursday cases := []struct { expr string want time.Time }{ {"0 1 * * *", time.Date(2026, 9, 18, 1, 0, 0, 0, loc)}, {"17 * * * *", time.Date(2026, 9, 17, 16, 17, 0, 0, loc)}, {"*/15 * * * *", time.Date(2026, 9, 17, 15, 45, 0, 0, loc)}, {"47 6 * * 7", time.Date(2026, 9, 20, 6, 47, 0, 0, loc)}, // Sunday as 7 {"52 6 1 * *", time.Date(2026, 10, 1, 6, 52, 0, 0, loc)}, // first of month {"24 0 1-7 * *", time.Date(2026, 10, 1, 0, 24, 0, 0, loc)}, // range {"30 3 * * 0", time.Date(2026, 9, 20, 3, 30, 0, 0, loc)}, // Sunday as 0 {"0 9 15 * mon", time.Date(2026, 9, 21, 9, 0, 0, 0, loc)}, // dom OR dow {"@daily", time.Date(2026, 9, 18, 0, 0, 0, 0, loc)}, {"@weekly", time.Date(2026, 9, 20, 0, 0, 0, 0, loc)}, {"0 0 29 feb *", time.Date(2028, 2, 29, 0, 0, 0, 0, loc)}, } for _, c := range cases { got, ok := nextRun(c.expr, from) if !ok || !got.Equal(c.want) { t.Errorf("%q: got %v ok=%v, want %v", c.expr, got, ok, c.want) } } if _, ok := nextRun("@reboot", from); ok { t.Error("@reboot should have no next run") } for _, bad := range []string{"* * * *", "60 * * * *", "a b c d e", "*/0 * * * *"} { if _, err := parseSchedule(bad); err == nil { t.Errorf("%q should fail", bad) } } } func TestParseLine(t *testing.T) { sch, u, cmd, ok := parseLine("25 6\t* * *\troot\ttest -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.daily; }", true) if !ok || sch != "25 6 * * *" || u != "root" || !strings_HasPrefix(cmd, "test -x") { t.Errorf("system line: %q %q %q %v", sch, u, cmd, ok) } if _, _, _, ok := parseLine("PATH=/usr/bin:/bin", true); ok { t.Error("env line parsed as job") } if _, _, _, ok := parseLine("# m h dom mon dow command", false); ok { t.Error("comment parsed as job") } if _, u, cmd, ok := parseLine("@reboot /usr/local/bin/thing --flag", false); !ok || u != "" || cmd != "/usr/local/bin/thing --flag" { t.Errorf("@reboot: %q %q %v", u, cmd, ok) } if got := outFile("foo >> /var/log/foo.log 2>&1"); got != "/var/log/foo.log" { t.Errorf("outFile: %q", got) } if c, in := cronPercent("echo hi%line1%line2"); c != "echo hi" || in != "line1\nline2" { t.Errorf("percent: %q %q", c, in) } if c, _ := cronPercent(`date +\%w`); c != "date +%w" { t.Errorf("escaped percent: %q", c) } } func strings_HasPrefix(s, p string) bool { return len(s) >= len(p) && s[:len(p)] == p }