package scheduler import ( "testing" "time" ) func TestParseCron(t *testing.T) { tests := []struct { expr string wantErr bool }{ {"0 8 * * *", false}, // 每天8点 {"*/10 * * * *", false}, // 每10分钟 {"0 9 * * 1", false}, // 每周一9点 {"30 14 1 * *", false}, // 每月1日14:30 {"0,30 * * * *", false}, // 每小时0分和30分 {"0 9-17 * * 1-5", false}, // 工作日9-17点每小时 {"bad", true}, // 无效 {"* * *", true}, // 字段不足 {"60 * * * *", true}, // 分钟超范围 {"* 25 * * *", true}, // 小时超范围 } for _, tt := range tests { _, err := ParseCron(tt.expr) if (err != nil) != tt.wantErr { t.Errorf("ParseCron(%q) error=%v, wantErr=%v", tt.expr, err, tt.wantErr) } } } func TestCronMatch(t *testing.T) { // 2026-03-07 是周六(weekday=6) base := time.Date(2026, 3, 7, 8, 0, 0, 0, time.Local) tests := []struct { expr string time time.Time want bool }{ // 每天8:00 {"0 8 * * *", base, true}, {"0 8 * * *", base.Add(1 * time.Minute), false}, // 8:01 不匹配 {"0 8 * * *", base.Add(-1 * time.Hour), false}, // 7:00 不匹配 // 每10分钟 {"*/10 * * * *", time.Date(2026, 3, 7, 8, 0, 0, 0, time.Local), true}, {"*/10 * * * *", time.Date(2026, 3, 7, 8, 10, 0, 0, time.Local), true}, {"*/10 * * * *", time.Date(2026, 3, 7, 8, 20, 0, 0, time.Local), true}, {"*/10 * * * *", time.Date(2026, 3, 7, 8, 5, 0, 0, time.Local), false}, // 每周一9:00(周六不匹配) {"0 9 * * 1", base, false}, {"0 9 * * 1", time.Date(2026, 3, 9, 9, 0, 0, 0, time.Local), true}, // 周一 // 每周六8:00 {"0 8 * * 6", base, true}, // 多值 {"0,30 * * * *", time.Date(2026, 3, 7, 8, 0, 0, 0, time.Local), true}, {"0,30 * * * *", time.Date(2026, 3, 7, 8, 30, 0, 0, time.Local), true}, {"0,30 * * * *", time.Date(2026, 3, 7, 8, 15, 0, 0, time.Local), false}, // 范围 {"0 9-17 * * *", time.Date(2026, 3, 7, 9, 0, 0, 0, time.Local), true}, {"0 9-17 * * *", time.Date(2026, 3, 7, 17, 0, 0, 0, time.Local), true}, {"0 9-17 * * *", time.Date(2026, 3, 7, 8, 0, 0, 0, time.Local), false}, {"0 9-17 * * *", time.Date(2026, 3, 7, 18, 0, 0, 0, time.Local), false}, // 步进范围 {"0 9-17/2 * * *", time.Date(2026, 3, 7, 9, 0, 0, 0, time.Local), true}, {"0 9-17/2 * * *", time.Date(2026, 3, 7, 11, 0, 0, 0, time.Local), true}, {"0 9-17/2 * * *", time.Date(2026, 3, 7, 10, 0, 0, 0, time.Local), false}, } for _, tt := range tests { expr, err := ParseCron(tt.expr) if err != nil { t.Fatalf("ParseCron(%q) 解析失败: %v", tt.expr, err) } got := expr.Match(tt.time) if got != tt.want { t.Errorf("CronExpr(%q).Match(%s) = %v, want %v", tt.expr, tt.time.Format("2006-01-02 15:04 Mon"), got, tt.want) } } } func TestMatchOnce(t *testing.T) { now := time.Date(2026, 3, 7, 14, 30, 0, 0, time.Local) tests := []struct { expr string want bool }{ {"2026-03-07 14:30", true}, {"2026-03-07 14:31", false}, {"2026-03-08 14:30", false}, {"bad-format", false}, } for _, tt := range tests { got := matchOnce(tt.expr, now) if got != tt.want { t.Errorf("matchOnce(%q, %s) = %v, want %v", tt.expr, now.Format("2006-01-02 15:04"), got, tt.want) } } } func TestParseField(t *testing.T) { tests := []struct { field string min int max int want []int wantNil bool }{ {"*", 0, 59, nil, true}, {"5", 0, 59, []int{5}, false}, {"1,3,5", 0, 59, []int{1, 3, 5}, false}, {"1-5", 0, 59, []int{1, 2, 3, 4, 5}, false}, {"*/15", 0, 59, []int{0, 15, 30, 45}, false}, {"10-20/3", 0, 59, []int{10, 13, 16, 19}, false}, } for _, tt := range tests { got, err := parseField(tt.field, tt.min, tt.max) if err != nil { t.Errorf("parseField(%q) 失败: %v", tt.field, err) continue } if tt.wantNil { if got != nil { t.Errorf("parseField(%q) = %v, want nil", tt.field, got) } continue } if len(got) != len(tt.want) { t.Errorf("parseField(%q) = %v, want %v", tt.field, got, tt.want) continue } for i := range got { if got[i] != tt.want[i] { t.Errorf("parseField(%q)[%d] = %d, want %d", tt.field, i, got[i], tt.want[i]) } } } }