Cron tab: crontabs, cron.d, user crontabs, run-parts dirs and systemd timers in one list; next/last run, run now, enable/disable, in-place editing, cron log and output file
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
bc891b1042
commit
41c2f05535
@@ -0,0 +1,68 @@
|
||||
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 }
|
||||
Reference in New Issue
Block a user