hyapp-server/services/cron-service/internal/scheduler/scheduler_synctest_test.go
2026-05-12 09:53:20 +08:00

102 lines
2.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package scheduler
import (
"context"
"sync"
"testing"
"testing/synctest"
"time"
"hyapp/services/cron-service/internal/config"
mysqlstorage "hyapp/services/cron-service/internal/storage/mysql"
)
type synctestStore struct {
mu sync.Mutex
leases int
started []mysqlstorage.TaskRun
finished []mysqlstorage.TaskRun
}
func (s *synctestStore) TryAcquireTaskLease(_ context.Context, _ string, _ string, _ string, _ int64, _ time.Duration) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
s.leases++
return true, nil
}
func (s *synctestStore) StartTaskRun(_ context.Context, run mysqlstorage.TaskRun) error {
s.mu.Lock()
defer s.mu.Unlock()
s.started = append(s.started, run)
return nil
}
func (s *synctestStore) FinishTaskRun(_ context.Context, run mysqlstorage.TaskRun) error {
s.mu.Lock()
defer s.mu.Unlock()
s.finished = append(s.finished, run)
return nil
}
// TestSchedulerRunUsesSynctestClock 锁定 cron loop 的首轮立即执行和 interval 退避。
// synctest 的虚拟时钟让 worker/timer 测试不依赖真实 sleep避免调度抖动污染 CI。
func TestSchedulerRunUsesSynctestClock(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
store := &synctestStore{}
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
var mu sync.Mutex
runTimes := make([]time.Time, 0, 2)
s := New("node-a", map[string]config.TaskConfig{
"room_outbox": {
Enabled: true,
AppCodes: []string{"lalu"},
Interval: "1s",
Timeout: "100ms",
LockTTL: "5s",
BatchSize: 8,
},
}, store, map[string]Handler{
"room_outbox": func(_ context.Context, request BatchRequest) (BatchResult, error) {
mu.Lock()
defer mu.Unlock()
if request.BatchSize != 8 || request.AppCode != "lalu" || request.WorkerID != "node-a" {
t.Fatalf("unexpected request: %+v", request)
}
runTimes = append(runTimes, time.Now())
return BatchResult{ProcessedCount: 1}, nil
},
})
go s.Run(ctx)
synctest.Wait()
mu.Lock()
if len(runTimes) != 1 {
t.Fatalf("first run count = %d, want 1", len(runTimes))
}
firstRunAt := runTimes[0]
mu.Unlock()
time.Sleep(time.Second)
synctest.Wait()
mu.Lock()
if len(runTimes) != 2 {
t.Fatalf("second run count = %d, want 2", len(runTimes))
}
if got := runTimes[1].Sub(firstRunAt); got != time.Second {
t.Fatalf("interval = %s, want 1s", got)
}
mu.Unlock()
cancel()
synctest.Wait()
store.mu.Lock()
defer store.mu.Unlock()
if store.leases != 2 || len(store.started) != 2 || len(store.finished) != 2 {
t.Fatalf("store calls: leases=%d started=%d finished=%d", store.leases, len(store.started), len(store.finished))
}
})
}