diff --git a/clock/clock.go b/clock/clock.go index 48ab2a17..ca329adc 100644 --- a/clock/clock.go +++ b/clock/clock.go @@ -1,3 +1,5 @@ +//go:build !holster_test_mode + // Package clock provides the same functions as the system package time. In // production it forwards all calls to the system time package, but in tests // the time can be frozen by calling Freeze function and from that point it has @@ -82,13 +84,6 @@ func After(d time.Duration) <-chan time.Time { return provider.After(d) } -// Timer see time.Timer. -type Timer interface { - C() <-chan time.Time - Stop() bool - Reset(d time.Duration) bool -} - // NewTimer see time.NewTimer. func NewTimer(d time.Duration) Timer { return provider.NewTimer(d) @@ -99,12 +94,6 @@ func AfterFunc(d time.Duration, f func()) Timer { return provider.AfterFunc(d, f) } -// Ticker see time.Ticker. -type Ticker interface { - C() <-chan time.Time - Stop() -} - // NewTicker see time.Ticker. func NewTicker(d time.Duration) Ticker { return provider.NewTicker(d) @@ -114,22 +103,3 @@ func NewTicker(d time.Duration) Ticker { func Tick(d time.Duration) <-chan time.Time { return provider.Tick(d) } - -// NewStoppedTimer returns a stopped timer. Call Reset to get it ticking. -func NewStoppedTimer() Timer { - t := NewTimer(42 * time.Hour) - t.Stop() - return t -} - -// Clock is an interface that mimics the one of the SDK time package. -type Clock interface { - Now() time.Time - Sleep(d time.Duration) - After(d time.Duration) <-chan time.Time - NewTimer(d time.Duration) Timer - AfterFunc(d time.Duration, f func()) Timer - NewTicker(d time.Duration) Ticker - Tick(d time.Duration) <-chan time.Time - Wait4Scheduled(n int, timeout time.Duration) bool -} diff --git a/clock/clock_mutex.go b/clock/clock_mutex.go new file mode 100644 index 00000000..f7f87080 --- /dev/null +++ b/clock/clock_mutex.go @@ -0,0 +1,131 @@ +//go:build holster_test_mode + +// Package clock provides the same functions as the system package time. In +// production it forwards all calls to the system time package, but in tests +// the time can be frozen by calling Freeze function and from that point it has +// to be advanced manually with Advance function making all scheduled calls +// deterministic. +// +// The functions provided by the package have the same parameters and return +// values as their system counterparts with a few exceptions. Where either +// *time.Timer or *time.Ticker is returned by a system function, the clock +// package counterpart returns clock.Timer or clock.Ticker interface +// respectively. The interfaces provide API as respective structs except C is +// not a channel, but a function that returns <-chan time.Time. +package clock + +import ( + "sync" + "time" +) + +var ( + frozenAt time.Time + realtime = &systemTime{} + provider Clock = realtime + rwMutex = sync.RWMutex{} +) + +// Freeze after this function is called all time related functions start +// generate deterministic timers that are triggered by Advance function. It is +// supposed to be used in tests only. Returns an Unfreezer so it can be a +// one-liner in tests: defer clock.Freeze(clock.Now()).Unfreeze() +func Freeze(now time.Time) Unfreezer { + frozenAt = now.UTC() + rwMutex.Lock() + defer rwMutex.Unlock() + provider = &frozenTime{now: now} + return Unfreezer{} +} + +type Unfreezer struct{} + +func (u Unfreezer) Unfreeze() { + Unfreeze() +} + +// Unfreeze reverses effect of Freeze. +func Unfreeze() { + rwMutex.Lock() + defer rwMutex.Unlock() + provider = realtime +} + +// Realtime returns a clock provider wrapping the SDK's time package. It is +// supposed to be used in tests when time is frozen to schedule test timeouts. +func Realtime() Clock { + return realtime +} + +// Makes the deterministic time move forward by the specified duration, firing +// timers along the way in the natural order. It returns how much time has +// passed since it was frozen. So you can assert on the return value in tests +// to make it explicit where you stand on the deterministic time scale. +func Advance(d time.Duration) time.Duration { + rwMutex.RLock() + ft, ok := provider.(*frozenTime) + rwMutex.RUnlock() + if !ok { + panic("Freeze time first!") + } + ft.advance(d) + return Now().UTC().Sub(frozenAt) +} + +// Wait4Scheduled blocks until either there are n or more scheduled events, or +// the timeout elapses. It returns true if the wait condition has been met +// before the timeout expired, false otherwise. +func Wait4Scheduled(count int, timeout time.Duration) bool { + rwMutex.RLock() + defer rwMutex.RUnlock() + return provider.Wait4Scheduled(count, timeout) +} + +// Now see time.Now. +func Now() time.Time { + rwMutex.RLock() + defer rwMutex.RUnlock() + return provider.Now() +} + +// Sleep see time.Sleep. +func Sleep(d time.Duration) { + rwMutex.RLock() + defer rwMutex.RUnlock() + provider.Sleep(d) +} + +// After see time.After. +func After(d time.Duration) <-chan time.Time { + rwMutex.RLock() + defer rwMutex.RUnlock() + return provider.After(d) +} + +// NewTimer see time.NewTimer. +func NewTimer(d time.Duration) Timer { + rwMutex.RLock() + defer rwMutex.RUnlock() + return provider.NewTimer(d) +} + +// AfterFunc see time.AfterFunc. +func AfterFunc(d time.Duration, f func()) Timer { + rwMutex.RLock() + defer rwMutex.RUnlock() + return provider.AfterFunc(d, f) +} + +// NewTicker see time.Ticker. +func NewTicker(d time.Duration) Ticker { + rwMutex.RLock() + defer rwMutex.RUnlock() + return provider.NewTicker(d) +} + +// Tick see time.Tick. +func Tick(d time.Duration) <-chan time.Time { + rwMutex.RLock() + defer rwMutex.RUnlock() + return provider.Tick(d) +} diff --git a/clock/interface.go b/clock/interface.go new file mode 100644 index 00000000..15f5ca1b --- /dev/null +++ b/clock/interface.go @@ -0,0 +1,35 @@ +package clock + +import "time" + +// Timer see time.Timer. +type Timer interface { + C() <-chan time.Time + Stop() bool + Reset(d time.Duration) bool +} + +// Ticker see time.Ticker. +type Ticker interface { + C() <-chan time.Time + Stop() +} + +// NewStoppedTimer returns a stopped timer. Call Reset to get it ticking. +func NewStoppedTimer() Timer { + t := NewTimer(42 * time.Hour) + t.Stop() + return t +} + +// Clock is an interface that mimics the one of the SDK time package. +type Clock interface { + Now() time.Time + Sleep(d time.Duration) + After(d time.Duration) <-chan time.Time + NewTimer(d time.Duration) Timer + AfterFunc(d time.Duration, f func()) Timer + NewTicker(d time.Duration) Ticker + Tick(d time.Duration) <-chan time.Time + Wait4Scheduled(n int, timeout time.Duration) bool +} diff --git a/go.sum b/go.sum index 67488e4f..ff9e70db 100644 --- a/go.sum +++ b/go.sum @@ -53,11 +53,8 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -227,12 +224,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.1.7 h1:1Lz7FaOfNuV8ZDOIFzLjzZn/aR+Q/F5OIwzqiXA2j78= -github.com/uptrace/opentelemetry-go-extra/otellogrus v0.1.7/go.mod h1:cBtIJGc/SoEXBaNdAlF75dSdwuXDe2xv2fG4r0UV7Wg= github.com/uptrace/opentelemetry-go-extra/otellogrus v0.1.9 h1:kQc7EjbDgfsy2bUYRQ/fCdtVOke8bPSWBwHY4FvN0Pg= github.com/uptrace/opentelemetry-go-extra/otellogrus v0.1.9/go.mod h1:ocTJjrGIZZpk4GcJlOZ5Iaw4jPqz65YbBbLp/cKlWPk= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.1.7 h1:GxcTFwIDGO48uLODBNVrs+gmlghqIZuk0OaPN1Ogd2Y= -github.com/uptrace/opentelemetry-go-extra/otelutil v0.1.7/go.mod h1:VoJEtpolPhk7ThZaHZR+3OQe/e3M1Oz3vJymTInLwmg= github.com/uptrace/opentelemetry-go-extra/otelutil v0.1.9 h1:Z5KSytLgwOBMZWwCPGeW2Q7olCBj0yWH+uNXtogjYM0= github.com/uptrace/opentelemetry-go-extra/otelutil v0.1.9/go.mod h1:Q9Auy29b37RW2Y00aYqjaSUcKoYUVN/+0BCtLU3eMQQ= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -245,20 +238,14 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3 go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.opentelemetry.io/otel v1.0.1/go.mod h1:OPEOD4jIT2SlZPMmwT6FqZz2C0ZNdQqiWcoK6M0SNFU= -go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.4.1 h1:QbINgGDDcoQUoMJa2mMaWno49lja9sHwp6aoa2n3a4g= go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= -go.opentelemetry.io/otel/exporters/jaeger v1.3.0 h1:HfydzioALdtcB26H5WHc4K47iTETJCdloL7VN579/L0= -go.opentelemetry.io/otel/exporters/jaeger v1.3.0/go.mod h1:KoYHi1BtkUPncGSRtCe/eh1ijsnePhSkxwzz07vU0Fc= go.opentelemetry.io/otel/exporters/jaeger v1.4.1 h1:VHCK+2yTZDqDaVXj7JH2Z/khptuydo6C0ttBh2bxAbc= go.opentelemetry.io/otel/exporters/jaeger v1.4.1/go.mod h1:ZW7vkOu9nC1CxsD8bHNHCia5JUbwP39vxgd1q4Z5rCI= go.opentelemetry.io/otel/sdk v1.0.1/go.mod h1:HrdXne+BiwsOHYYkBE5ysIcv2bvdZstxzmCQhxTcZkI= -go.opentelemetry.io/otel/sdk v1.3.0 h1:3278edCoH89MEJ0Ky8WQXVmDQv3FX4ZJ3Pp+9fJreAI= -go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk v1.4.1 h1:J7EaW71E0v87qflB4cDolaqq3AcujGrtyIPGQoZOB0Y= go.opentelemetry.io/otel/sdk v1.4.1/go.mod h1:NBwHDgDIBYjwK2WNu1OPgsIc2IJzmBXNnvIJxJc8BpE= go.opentelemetry.io/otel/trace v1.0.1/go.mod h1:5g4i4fKLaX2BQpSBsxw8YYcgKpMMSW3x7ZTuYBr3sUk= -go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/otel/trace v1.4.1 h1:O+16qcdTrT7zxv2J6GejTPFinSwA++cYerC5iSiF8EQ= go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -340,11 +327,11 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486 h1:5hpz5aRr+W1erYCL5JRhSUBJRph7l9XkNveoExlrKYk= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7 h1:8IVLkfbr2cLhv0a/vKq4UFUcJym8RmDoDboxCFWEjYE= golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=