package clock import ( "sync" "time" ) // Clock abstracts time operations for deterministic testing. type Clock interface { Now() time.Time Since(t time.Time) time.Duration NewTimer(d time.Duration) Timer After(d time.Duration) <-chan time.Time } // Timer abstracts time.Timer for testability. type Timer interface { C() <-chan time.Time Stop() bool Reset(d time.Duration) bool } // System returns a Clock backed by the real system time. func System() Clock { return systemClock{} } type systemClock struct{} func (systemClock) Now() time.Time { return time.Now() } func (systemClock) Since(t time.Time) time.Duration { return time.Since(t) } func (systemClock) NewTimer(d time.Duration) Timer { return &systemTimer{t: time.NewTimer(d)} } func (systemClock) After(d time.Duration) <-chan time.Time { return time.After(d) } type systemTimer struct{ t *time.Timer } func (s *systemTimer) C() <-chan time.Time { return s.t.C } func (s *systemTimer) Stop() bool { return s.t.Stop() } func (s *systemTimer) Reset(d time.Duration) bool { return s.t.Reset(d) } // Mock returns a manually-controlled Clock for tests. func Mock(now time.Time) *MockClock { return &MockClock{now: now} } // MockClock is a deterministic clock for testing. type MockClock struct { mu sync.Mutex now time.Time timers []*mockTimer } func (m *MockClock) Now() time.Time { m.mu.Lock() defer m.mu.Unlock() return m.now } func (m *MockClock) Since(t time.Time) time.Duration { m.mu.Lock() defer m.mu.Unlock() return m.now.Sub(t) } func (m *MockClock) NewTimer(d time.Duration) Timer { m.mu.Lock() defer m.mu.Unlock() t := &mockTimer{ ch: make(chan time.Time, 1), deadline: m.now.Add(d), active: true, } m.timers = append(m.timers, t) if d <= 0 { t.fire(m.now) } return t } func (m *MockClock) After(d time.Duration) <-chan time.Time { return m.NewTimer(d).C() } // Advance moves the clock forward by d and fires any expired timers. func (m *MockClock) Advance(d time.Duration) { m.mu.Lock() m.now = m.now.Add(d) now := m.now timers := m.timers m.mu.Unlock() for _, t := range timers { t.mu.Lock() if t.active && !now.Before(t.deadline) { t.fire(now) } t.mu.Unlock() } } // Set sets the clock to an absolute time and fires any expired timers. func (m *MockClock) Set(t time.Time) { m.mu.Lock() m.now = t now := m.now timers := m.timers m.mu.Unlock() for _, tmr := range timers { tmr.mu.Lock() if tmr.active && !now.Before(tmr.deadline) { tmr.fire(now) } tmr.mu.Unlock() } } type mockTimer struct { mu sync.Mutex ch chan time.Time deadline time.Time active bool } func (t *mockTimer) C() <-chan time.Time { return t.ch } func (t *mockTimer) Stop() bool { t.mu.Lock() defer t.mu.Unlock() was := t.active t.active = false return was } func (t *mockTimer) Reset(d time.Duration) bool { t.mu.Lock() defer t.mu.Unlock() was := t.active t.active = true // Note: deadline will be recalculated on next Advance t.deadline = time.Now().Add(d) // placeholder; mock users should use Advance return was } // fire sends the time on the channel. Caller must hold t.mu. func (t *mockTimer) fire(now time.Time) { t.active = false select { case t.ch <- now: default: } }