package activator import ( "context" "testing" "time" "github.com/carlosframework/platform/internal/registry" ) // The plans idle hook (plans 2): a plan may only shorten an entry's idle // window, never lengthen it, and never touches an always-on worker. func idleFor(d time.Duration) func(registry.Route) time.Duration { return func(registry.Route) time.Duration { return d } } func wakeInstance(t *testing.T, a *Service, host string) { t.Helper() release, err := a.beginRequest(context.Background(), testRoute(host)) if err != nil { t.Fatalf("beginRequest: %v", err) } release() } func TestIdleForShortensAnInstance(t *testing.T) { t.Parallel() a, _, clk, _ := testActivator(t, 15*time.Minute) a.SetIdleFor(idleFor(90 * time.Second)) wakeInstance(t, a, "one.example.com") clk.advance(80 * time.Second) a.sweepOnce() if a.entryCount() != 1 { t.Fatal("slept before the plan's 90s window") } clk.advance(11 * time.Second) a.sweepOnce() waitFor(t, "plan idle sleep", func() bool { return a.entryCount() == 0 }) } func TestPlanIdleOnlyShortens(t *testing.T) { t.Parallel() a, _, clk, _ := testActivator(t, 5*time.Minute) a.SetIdleFor(idleFor(15 * time.Minute)) wakeInstance(t, a, "one.example.com") clk.advance(5*time.Minute + time.Second) a.sweepOnce() waitFor(t, "box-default sleep", func() bool { return a.entryCount() == 0 }) } func TestIdleForShortensAWorkerToTheSmaller(t *testing.T) { t.Parallel() for _, c := range []struct { name string worker int plan, sleep time.Duration }{ {"plan shorter than the worker's own", 600, 90 * time.Second, 90 * time.Second}, {"worker's own shorter than the plan", 60, 90 * time.Second, 60 * time.Second}, } { a, _, clk, _ := testActivator(t, time.Hour) a.SetIdleFor(idleFor(c.plan)) rt := testWorkerRoute("bot.x.internal") rt.IdleSeconds = c.worker if err := a.WakeWorker(rt); err != nil { t.Fatalf("%s: WakeWorker: %v", c.name, err) } clk.advance(c.sleep - 5*time.Second) a.sweepOnce() if !a.entryAwake("bot.x.internal") { t.Fatalf("%s: slept early", c.name) } clk.advance(6 * time.Second) a.sweepOnce() waitFor(t, c.name, func() bool { return a.entryCount() == 0 }) } } func TestIdleForNilOrZeroChangesNothing(t *testing.T) { t.Parallel() for name, hook := range map[string]func(registry.Route) time.Duration{"nil": nil, "zero": idleFor(0)} { a, _, clk, _ := testActivator(t, 10*time.Second) a.SetIdleFor(hook) wakeInstance(t, a, "one.example.com") clk.advance(9 * time.Second) a.sweepOnce() if a.entryCount() != 1 { t.Fatalf("%s hook: slept before the box default", name) } clk.advance(2 * time.Second) a.sweepOnce() waitFor(t, name+" hook: default sleep", func() bool { return a.entryCount() == 0 }) } } func TestIdleForLeavesAlwaysOnWorkersAlone(t *testing.T) { t.Parallel() a, _, clk, _ := testActivator(t, time.Hour) a.SetIdleFor(idleFor(90 * time.Second)) rt := testWorkerRoute("bot.x.internal") rt.Hibernate = false if err := a.WakeWorker(rt); err != nil { t.Fatalf("WakeWorker: %v", err) } for i := 0; i < 5; i++ { clk.advance(time.Hour) a.sweepOnce() } if a.entryCount() != 1 { t.Fatal("an always-on worker was stopped by the plan's idle window") } }