package schedule import ( "testing" "time" ) func TestMisfireOverlapAndBoundedCatchup(t *testing.T) { start := stamp("2026-09-17T00:00:00Z") c, err := Compile(Rule{Kind: "FIXED_RATE", Timezone: "UTC", StartAt: start, IntervalMS: 1000}) if err != nil { t.Fatal(err) } base := Policy{Misfire: "CATCH_UP", Overlap: "PARALLEL", CatchupLimit: 3, CatchupWindowMS: 10000, GraceMS: 1000} for _, tc := range []struct { name, misfire, overlap string active bool kinds []string next int blocked bool }{ {"catchup", "CATCH_UP", "PARALLEL", false, []string{"SKIPPED", "FIRED", "FIRED", "FIRED"}, 13, false}, {"coalesce", "COALESCE", "PARALLEL", false, []string{"SKIPPED", "COALESCED"}, 21, false}, {"skip", "SKIP", "PARALLEL", false, []string{"SKIPPED", "SKIPPED", "FIRED", "FIRED"}, 21, false}, {"serial_wait", "CATCH_UP", "SERIAL", true, []string{"SKIPPED"}, 10, true}, {"serial_one", "CATCH_UP", "SERIAL", false, []string{"SKIPPED", "FIRED"}, 11, false}, {"overlap_skip", "CATCH_UP", "SKIP", true, []string{"SKIPPED", "SKIPPED"}, 21, false}, } { t.Run(tc.name, func(t *testing.T) { p := base p.Misfire = tc.misfire p.Overlap = tc.overlap out, err := Plan(c, p, start, start.Add(20*time.Second), nil, tc.active) if err != nil || out.Next == nil || !out.Next.Equal(start.Add(time.Duration(tc.next)*time.Second)) || out.Blocked != tc.blocked || len(out.Decisions) != len(tc.kinds) { t.Fatalf("unexpected batch %+v %v", out, err) } for i, kind := range tc.kinds { if out.Decisions[i].Kind != kind { t.Fatal("wrong decision", out.Decisions) } } }) } // 旧版本只覆盖生效边界之前,不能把新版本的时间合并进去。 before := start.Add(15 * time.Second) base.Misfire = "COALESCE" out, err := Plan(c, base, start, start.Add(20*time.Second), &before, false) if err != nil || out.Next != nil || len(out.Decisions) != 2 || !out.Decisions[1].CoveredUntil.Equal(before) { t.Fatalf("version boundary lost: %+v %v", out, err) } // 未到期不产生记录;同一快照重算得到同一原触发点。 out, err = Plan(c, base, start, start.Add(-time.Second), nil, false) if err != nil || len(out.Decisions) != 0 || out.Next == nil || !out.Next.Equal(start) { t.Fatal("future point processed") } }