You've already forked agentic-coding-workflow
feat(infra/clock): injectable Clock with Real and Fake implementations
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
// Package clock provides an injectable time source. Production code uses Real();
|
||||||
|
// tests use NewFake(t) to make time-based logic deterministic.
|
||||||
|
package clock
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Clock is the abstraction over the system clock. Implementations must be
|
||||||
|
// safe for concurrent use.
|
||||||
|
type Clock interface {
|
||||||
|
Now() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type realClock struct{}
|
||||||
|
|
||||||
|
// Real returns a Clock backed by time.Now.
|
||||||
|
func Real() Clock { return realClock{} }
|
||||||
|
|
||||||
|
func (realClock) Now() time.Time { return time.Now() }
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package clock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRealClock_Now(t *testing.T) {
|
||||||
|
c := Real()
|
||||||
|
now := c.Now()
|
||||||
|
assert.WithinDuration(t, time.Now(), now, time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeClock_NowReturnsSetTime(t *testing.T) {
|
||||||
|
t0 := time.Date(2026, 5, 5, 10, 0, 0, 0, time.UTC)
|
||||||
|
c := NewFake(t0)
|
||||||
|
assert.Equal(t, t0, c.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeClock_Advance(t *testing.T) {
|
||||||
|
t0 := time.Date(2026, 5, 5, 10, 0, 0, 0, time.UTC)
|
||||||
|
c := NewFake(t0)
|
||||||
|
c.Advance(2 * time.Hour)
|
||||||
|
assert.Equal(t, t0.Add(2*time.Hour), c.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeClock_AdvanceConcurrent(t *testing.T) {
|
||||||
|
c := NewFake(time.Now())
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
c.Advance(time.Microsecond)
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
_ = c.Now()
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFakeClock_Set(t *testing.T) {
|
||||||
|
c := NewFake(time.Date(2026, 5, 5, 10, 0, 0, 0, time.UTC))
|
||||||
|
t1 := time.Date(2027, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
c.Set(t1)
|
||||||
|
assert.Equal(t, t1, c.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Clock = (*realClock)(nil)
|
||||||
|
var _ Clock = (*FakeClock)(nil)
|
||||||
|
var _ = require.New // keep import live; helper used by other packages
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package clock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FakeClock is a controllable Clock for tests. Construct with NewFake; advance
|
||||||
|
// time with Advance; jump to a specific time with Set. Safe for concurrent use.
|
||||||
|
type FakeClock struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
now time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFake returns a FakeClock initialized to t.
|
||||||
|
func NewFake(t time.Time) *FakeClock {
|
||||||
|
return &FakeClock{now: t}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *FakeClock) Now() time.Time {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
return c.now
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance moves the clock forward by d.
|
||||||
|
func (c *FakeClock) Advance(d time.Duration) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.now = c.now.Add(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set jumps the clock to t.
|
||||||
|
func (c *FakeClock) Set(t time.Time) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
c.now = t
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user