One kind for the module's own code, with three modes
The first cut of this added a `daemon` for the long-running case alone. That would have meant a new vocabulary entry for each of the others — a scheduled task, a run-once migration, a health check — when they are one thing run at different cadences. That is a field, not four entries in a vocabulary where every entry widens what a compromised control plane can express. So it mirrors a container exactly, because it IS a container's twin: the same intent, hosted by the machine's own supervisor instead of a runtime. Stays up, runs once, or runs on a schedule. Tools, hooks and event consumers are not further modes. They are loaded by a tool host, which is itself a process that stays up — so the generic case already covers them, which is the test of whether it is generic. A scheduled process gets a timer and a unit that finishes; a long-running one gets a unit that is restarted when it exits. Getting that wrong either way is a second copy running continuously between fires, or a schedule that never fires. The modes are exclusive and validation says so near the author: something that runs once does not run on a schedule, and something not running between fires cannot be restarted when a file changes. A missed fire happens when the machine comes back rather than being skipped, which is the difference between a machine that was down and a schedule that quietly stopped. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -11,19 +11,19 @@ import (
|
||||
|
||||
func TestParseCronRefusesMalformed(t *testing.T) {
|
||||
for _, expr := range []string{
|
||||
"", // nothing
|
||||
"* * * *", // four fields
|
||||
"* * * * * *", // six fields
|
||||
"60 * * * *", // minute out of range
|
||||
"* 24 * * *", // hour out of range
|
||||
"* * 0 * *", // day-of-month below 1
|
||||
"* * 32 * *", // day-of-month above 31
|
||||
"* * * 13 *", // month out of range
|
||||
"* * * * 8", // day-of-week above 7
|
||||
"a * * * *", // not a number
|
||||
"*/0 * * * *", // zero step
|
||||
"5-1 * * * *", // inverted range
|
||||
"1,,2 * * * *", // empty element
|
||||
"", // nothing
|
||||
"* * * *", // four fields
|
||||
"* * * * * *", // six fields
|
||||
"60 * * * *", // minute out of range
|
||||
"* 24 * * *", // hour out of range
|
||||
"* * 0 * *", // day-of-month below 1
|
||||
"* * 32 * *", // day-of-month above 31
|
||||
"* * * 13 *", // month out of range
|
||||
"* * * * 8", // day-of-week above 7
|
||||
"a * * * *", // not a number
|
||||
"*/0 * * * *", // zero step
|
||||
"5-1 * * * *", // inverted range
|
||||
"1,,2 * * * *", // empty element
|
||||
} {
|
||||
if _, err := ParseCron(expr); err == nil {
|
||||
t.Errorf("a malformed cron %q was accepted", expr)
|
||||
@@ -33,13 +33,13 @@ func TestParseCronRefusesMalformed(t *testing.T) {
|
||||
|
||||
func TestParseCronAcceptsTheOrdinaryForms(t *testing.T) {
|
||||
for _, expr := range []string{
|
||||
"* * * * *", // every minute
|
||||
"0 3 * * *", // 03:00 daily
|
||||
"*/15 * * * *", // every 15 minutes
|
||||
"0 0 1 1 *", // new year
|
||||
"0 9-17 * * 1-5", // business hours, weekdays
|
||||
"0 0 * * 7", // Sunday as 7
|
||||
"0,30 * * * *", // twice an hour
|
||||
"* * * * *", // every minute
|
||||
"0 3 * * *", // 03:00 daily
|
||||
"*/15 * * * *", // every 15 minutes
|
||||
"0 0 1 1 *", // new year
|
||||
"0 9-17 * * 1-5", // business hours, weekdays
|
||||
"0 0 * * 7", // Sunday as 7
|
||||
"0,30 * * * *", // twice an hour
|
||||
} {
|
||||
if _, err := ParseCron(expr); err != nil {
|
||||
t.Errorf("a valid cron %q was refused: %v", expr, err)
|
||||
|
||||
Reference in New Issue
Block a user