├── .gitignore
├── go.mod
├── timexmock
├── mocked.go
├── ticker.go
├── timer.go
└── implementation.go
├── benchmark_test.go
├── .travis.yml
├── implementation.go
├── ticker.go
├── timer.go
├── CHANGELOG.md
├── go.sum
├── Makefile
├── timextest
├── mocked_ticker.go
├── mocked_ticker_test.go
├── mocked_timer_test.go
├── mocked_timer.go
├── test_implementation.go
└── example_test.go
├── funcs_disabled.go
├── default.go
├── funcs.go
├── funcs_disabled_test.go
├── default_test.go
├── .golangci.yaml
├── funcs_test.go
├── README.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage.out
2 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/cabify/timex
2 |
3 | go 1.13
4 |
5 | require github.com/stretchr/testify v1.4.0
6 |
--------------------------------------------------------------------------------
/timexmock/mocked.go:
--------------------------------------------------------------------------------
1 | package timexmock
2 |
3 | import "github.com/cabify/timex"
4 |
5 | // Mocked runs the provided function with timex mocked, and then restores
6 | // the default implementation
7 | func Mocked(f func(*Implementation)) {
8 | mocked := &Implementation{}
9 | restore := timex.Override(mocked)
10 | defer restore()
11 | f(mocked)
12 | }
13 |
--------------------------------------------------------------------------------
/benchmark_test.go:
--------------------------------------------------------------------------------
1 | package timex_test
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/cabify/timex"
8 | )
9 |
10 | var dontOptimizeMePlease time.Time
11 |
12 | func BenchmarkTimeNow(b *testing.B) {
13 | for i := 0; i <= b.N; i++ {
14 | dontOptimizeMePlease = time.Now()
15 | }
16 | }
17 |
18 | func BenchmarkTimexNow(b *testing.B) {
19 | for i := 0; i <= b.N; i++ {
20 | dontOptimizeMePlease = timex.Now()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - "1.12.x"
5 | - "1.13.x"
6 |
7 | env:
8 | - GO111MODULE=on
9 |
10 | install:
11 | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.20.1
12 |
13 | script:
14 | - make test
15 | - golangci-lint run
16 |
17 | after_success:
18 | - go get github.com/mattn/goveralls
19 | - goveralls -coverprofile=coverage.out -service=travis-ci
20 |
--------------------------------------------------------------------------------
/implementation.go:
--------------------------------------------------------------------------------
1 | package timex
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // Implementation defines the methods we delegate
8 | type Implementation interface {
9 | Now() time.Time
10 | Since(t time.Time) time.Duration
11 | Until(t time.Time) time.Duration
12 |
13 | Sleep(d time.Duration)
14 |
15 | After(d time.Duration) <-chan time.Time
16 | AfterFunc(d time.Duration, f func()) Timer
17 |
18 | NewTicker(d time.Duration) Ticker
19 | NewTimer(d time.Duration) Timer
20 | }
21 |
22 | //go:generate mockery -case underscore -outpkg timexmock -output timexmock -name Implementation
23 | var _ Implementation = Default{}
24 |
--------------------------------------------------------------------------------
/ticker.go:
--------------------------------------------------------------------------------
1 | package timex
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // Ticker is an interface similar to what time.Ticker provides, but with C as a function returning the channel
8 | // instead of accessing a plain struct property, so we can mock it.
9 | // See time.Ticker docs for more details
10 | type Ticker interface {
11 | // C returns the channel where each tick will be signaled, like Ticker.C in time package
12 | C() <-chan time.Time
13 |
14 | // Stop stops the ticker, no more ticks will be received in `C()`
15 | Stop()
16 | }
17 |
18 | //go:generate mockery -case underscore -outpkg timexmock -output timexmock -name Ticker
19 | type ticker struct {
20 | *time.Ticker
21 | }
22 |
23 | func (t ticker) C() <-chan time.Time { return t.Ticker.C }
24 |
--------------------------------------------------------------------------------
/timexmock/ticker.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v1.0.0. DO NOT EDIT.
2 |
3 | package timexmock
4 |
5 | import (
6 | time "time"
7 |
8 | mock "github.com/stretchr/testify/mock"
9 | )
10 |
11 | // Ticker is an autogenerated mock type for the Ticker type
12 | type Ticker struct {
13 | mock.Mock
14 | }
15 |
16 | // C provides a mock function with given fields:
17 | func (_m *Ticker) C() <-chan time.Time {
18 | ret := _m.Called()
19 |
20 | var r0 <-chan time.Time
21 | if rf, ok := ret.Get(0).(func() <-chan time.Time); ok {
22 | r0 = rf()
23 | } else {
24 | if ret.Get(0) != nil {
25 | r0 = ret.Get(0).(<-chan time.Time)
26 | }
27 | }
28 |
29 | return r0
30 | }
31 |
32 | // Stop provides a mock function with given fields:
33 | func (_m *Ticker) Stop() {
34 | _m.Called()
35 | }
36 |
--------------------------------------------------------------------------------
/timer.go:
--------------------------------------------------------------------------------
1 | package timex
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // Timer is an interface similar to what time.Timer provides, but with C as a function returning the channel
8 | // instead of accessing a plain struct property, so we can mock it.
9 | // See time.Timer docs for more details
10 | type Timer interface {
11 | // C returns the channel where the tick will be signaled, like Timer.C in time package
12 | C() <-chan time.Time
13 |
14 | // Stop stops the timer, see exact docs on time.Timer for information about channel draining when Stop returns false
15 | Stop() bool
16 | }
17 |
18 | //go:generate mockery -case underscore -outpkg timexmock -output timexmock -name Timer
19 | type timer struct {
20 | *time.Timer
21 | }
22 |
23 | func (t timer) C() <-chan time.Time { return t.Timer.C }
24 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [Unreleased]
8 |
9 | ## [1.1.0] - 2020-08-03
10 | ### Added
11 | - Default implementation is now exposed as `timex.Default` [#2](https://github.com/cabify/timex/pull/2)
12 |
13 | ## [1.0.0] - 2020-01-07
14 | ### Added
15 | - Initial commit with all the previous implementation squashed into one commit.
16 |
17 |
18 | [Unreleased]: https://github.com/cabify/timex/compare/v1.1.0...HEAD
19 | [1.1.0]: https://github.com/cabify/timex/compare/v1.0.0...v1.1.0
20 | [1.0.0]: https://github.com/cabify/timex/compare/v1.0.0
21 |
22 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
5 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
6 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
7 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
10 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
11 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test help fmt report-coveralls benchmark lint
2 |
3 | help: ## Show the help text
4 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[93m %s\n", $$1, $$2}'
5 |
6 | test: ## Run unit tests
7 | @echo "Testing with timex_disable tag (only root package)"
8 | @go test -tags=timex_disable -race .
9 | @echo "Testing without timex_disable tag (normal)"
10 | @go test -coverprofile=coverage.out -covermode=atomic -race ./...
11 |
12 | lint:
13 | @golangci-lint run
14 |
15 | fmt: ## Format files
16 | @goimports -w $$(find . -name "*.go" -not -path "./vendor/*")
17 |
18 | benchmark: ## Run benchmarks
19 | @echo "Benchmarks with timex_disable tag"
20 | @go test -run=NONE -benchmem -benchtime=5s -bench=. -tags=timex_disable .
21 | @echo "Benchmarks without timex_disable tag (normal)"
22 | @go test -run=NONE -benchmem -benchtime=5s -bench=. .
23 |
--------------------------------------------------------------------------------
/timexmock/timer.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v1.0.0. DO NOT EDIT.
2 |
3 | package timexmock
4 |
5 | import (
6 | time "time"
7 |
8 | mock "github.com/stretchr/testify/mock"
9 | )
10 |
11 | // Timer is an autogenerated mock type for the Timer type
12 | type Timer struct {
13 | mock.Mock
14 | }
15 |
16 | // C provides a mock function with given fields:
17 | func (_m *Timer) C() <-chan time.Time {
18 | ret := _m.Called()
19 |
20 | var r0 <-chan time.Time
21 | if rf, ok := ret.Get(0).(func() <-chan time.Time); ok {
22 | r0 = rf()
23 | } else {
24 | if ret.Get(0) != nil {
25 | r0 = ret.Get(0).(<-chan time.Time)
26 | }
27 | }
28 |
29 | return r0
30 | }
31 |
32 | // Stop provides a mock function with given fields:
33 | func (_m *Timer) Stop() bool {
34 | ret := _m.Called()
35 |
36 | var r0 bool
37 | if rf, ok := ret.Get(0).(func() bool); ok {
38 | r0 = rf()
39 | } else {
40 | r0 = ret.Get(0).(bool)
41 | }
42 |
43 | return r0
44 | }
45 |
--------------------------------------------------------------------------------
/timextest/mocked_ticker.go:
--------------------------------------------------------------------------------
1 | package timextest
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // MockedTicker implements a timex.Ticker and provides functions to control its behavior
8 | type MockedTicker struct {
9 | c chan time.Time
10 | stopped chan struct{}
11 | }
12 |
13 | func newMockedTicker() *MockedTicker {
14 | return &MockedTicker{
15 | c: make(chan time.Time),
16 | stopped: make(chan struct{}),
17 | }
18 | }
19 |
20 | // C implements timex.Ticker
21 | func (mt *MockedTicker) C() <-chan time.Time {
22 | return mt.c
23 | }
24 |
25 | // Stop implements timex.Ticker, and it will close the channel provided by StoppedChan
26 | func (mt *MockedTicker) Stop() {
27 | close(mt.stopped)
28 | }
29 |
30 | // StoppedChan will be closed once Stop is called
31 | func (mt *MockedTicker) StoppedChan() chan struct{} {
32 | return mt.stopped
33 | }
34 |
35 | // Tick will send the provided time through ticker.C()
36 | func (mt *MockedTicker) Tick(t time.Time) {
37 | select {
38 | case <-mt.stopped:
39 | panic("trying to tick on a stopped ticker, does your test have a race condition?")
40 | default:
41 | mt.c <- t
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/funcs_disabled.go:
--------------------------------------------------------------------------------
1 | // +build timex_disable
2 |
3 | package timex
4 |
5 | import (
6 | "time"
7 | )
8 |
9 | // Now can be used as a replacement of time.Now()
10 | func Now() time.Time { return time.Now() }
11 |
12 | // Since can be used as a replacement of time.Since()
13 | func Since(t time.Time) time.Duration { return time.Since(t) }
14 |
15 | // Until can be used as a replacement of time.Until()
16 | func Until(t time.Time) time.Duration { return time.Until(t) }
17 |
18 | // Sleep can be used as a replacement of time.Sleep()
19 | func Sleep(d time.Duration) { time.Sleep(d) }
20 |
21 | // After can be used a replacement of time.After()
22 | func After(d time.Duration) <-chan time.Time { return time.After(d) }
23 |
24 | // AfterFunc can be used as a replacement of time.AfterFunc()
25 | func AfterFunc(d time.Duration, f func()) Timer { return timer{time.AfterFunc(d, f)} }
26 |
27 | // NewTicker creates a new ticker as a replacement of time.NewTicker
28 | func NewTicker(d time.Duration) Ticker { return ticker{time.NewTicker(d)} }
29 |
30 | // NewTimer creates a new timer as a replacement of time.NewTimer
31 | func NewTimer(d time.Duration) Timer { return timer{time.NewTimer(d)} }
32 |
--------------------------------------------------------------------------------
/timextest/mocked_ticker_test.go:
--------------------------------------------------------------------------------
1 | package timextest_test
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/cabify/timex"
8 | "github.com/cabify/timex/timextest"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestMockedTicker_StoppedChan(t *testing.T) {
13 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
14 | go func() {
15 | ticker := timex.NewTicker(time.Second)
16 | ticker.Stop()
17 | }()
18 | mockedTicker := <-mockedtimex.NewTickerCalls
19 |
20 | select {
21 | case <-mockedTicker.Mock.StoppedChan():
22 | case <-time.After(time.Second):
23 | t.Errorf("Stop should have been called")
24 | }
25 | })
26 | }
27 |
28 | func TestMockedTicker_Tick(t *testing.T) {
29 | t.Run("panics on stopped ticker", func(t *testing.T) {
30 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
31 | go func() {
32 | ticker := timex.NewTicker(time.Second)
33 | ticker.Stop()
34 | }()
35 | mockedTicker := <-mockedtimex.NewTickerCalls
36 |
37 | <-mockedTicker.Mock.StoppedChan()
38 |
39 | assert.Panics(t, func() {
40 | mockedTicker.Mock.Tick(now)
41 | })
42 | })
43 | })
44 | }
45 |
--------------------------------------------------------------------------------
/timextest/mocked_timer_test.go:
--------------------------------------------------------------------------------
1 | package timextest_test
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/cabify/timex"
8 | "github.com/cabify/timex/timextest"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestMockedTimer_StoppedChan(t *testing.T) {
13 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
14 | stopValue := make(chan bool)
15 | go func() {
16 | ticker := timex.NewTimer(time.Second)
17 | stopValue <- ticker.Stop()
18 | }()
19 | mockedTicker := <-mockedtimex.NewTimerCalls
20 | mockedTicker.Mock.StopValue(true)
21 |
22 | select {
23 | case <-mockedTicker.Mock.StoppedChan():
24 | case <-time.After(time.Second):
25 | t.Errorf("Stop should have been called")
26 | }
27 |
28 | assert.True(t, <-stopValue)
29 | })
30 | }
31 |
32 | func TestMockedTimer_Trigger(t *testing.T) {
33 | t.Run("panics on stopped timer", func(t *testing.T) {
34 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
35 | go func() {
36 | ticker := timex.NewTimer(time.Second)
37 | ticker.Stop()
38 | }()
39 | mockedTimer := <-mockedtimex.NewTimerCalls
40 | mockedTimer.Mock.StopValue(true)
41 |
42 | <-mockedTimer.Mock.StoppedChan()
43 |
44 | assert.Panics(t, func() {
45 | mockedTimer.Mock.Trigger(now)
46 | })
47 | })
48 | })
49 | }
50 |
--------------------------------------------------------------------------------
/default.go:
--------------------------------------------------------------------------------
1 | package timex
2 |
3 | import "time"
4 |
5 | // Default uses time package functions
6 | type Default struct{}
7 |
8 | // Now can be used as a replacement of time.Now()
9 | func (Default) Now() time.Time {
10 | return time.Now()
11 | }
12 |
13 | // Since can be used as a replacement of time.Since()
14 | func (Default) Since(t time.Time) time.Duration {
15 | return time.Since(t)
16 | }
17 |
18 | // Until can be used as a replacement of time.Until()
19 | func (Default) Until(t time.Time) time.Duration {
20 | return time.Until(t)
21 | }
22 |
23 | // AfterFunc can be used as a replacement of time.AfterFunc()
24 | func (Default) AfterFunc(d time.Duration, f func()) Timer {
25 | return timer{time.AfterFunc(d, f)}
26 | }
27 |
28 | // Sleep can be used as a replacement of time.Sleep()
29 | func (Default) Sleep(d time.Duration) {
30 | time.Sleep(d)
31 | }
32 |
33 | // After can be used a replacement of time.After()
34 | func (Default) After(d time.Duration) <-chan time.Time {
35 | return time.After(d)
36 | }
37 |
38 | // NewTicker creates a new ticker as replacement of time.NewTicker()
39 | func (Default) NewTicker(d time.Duration) Ticker {
40 | return ticker{time.NewTicker(d)}
41 | }
42 |
43 | // NewTimer creates a new timer as replacement of time.NewTimer()
44 | func (Default) NewTimer(d time.Duration) Timer {
45 | return timer{time.NewTimer(d)}
46 | }
47 |
--------------------------------------------------------------------------------
/timextest/mocked_timer.go:
--------------------------------------------------------------------------------
1 | package timextest
2 |
3 | import (
4 | "sync"
5 | "time"
6 | )
7 |
8 | // MockedTimer implements a timex.Timer and provides functions to control its behavior
9 | type MockedTimer struct {
10 | c chan time.Time
11 | f func()
12 |
13 | stopped chan struct{}
14 |
15 | stopMutex sync.Mutex
16 | stopValue bool
17 | }
18 |
19 | func newMockedTimer() *MockedTimer {
20 | timer := &MockedTimer{
21 | c: make(chan time.Time),
22 | stopped: make(chan struct{}),
23 | }
24 | timer.stopMutex.Lock()
25 | return timer
26 | }
27 |
28 | func newMockedFuncTimer(f func()) *MockedTimer {
29 | timer := &MockedTimer{
30 | f: f,
31 | stopped: make(chan struct{}),
32 | }
33 | timer.stopMutex.Lock()
34 | return timer
35 | }
36 |
37 | // C implements timex.Timer
38 | func (mt *MockedTimer) C() <-chan time.Time {
39 | return mt.c
40 | }
41 |
42 | // Stop implements the timex.Timer, stop will not return until the value is set by StopValue()
43 | func (mt *MockedTimer) Stop() bool {
44 | close(mt.stopped)
45 | mt.stopMutex.Lock()
46 | defer mt.stopMutex.Unlock()
47 | return mt.stopValue
48 | }
49 |
50 | // StopValue sets the value to be returned by Stop(), it has to be called to allow
51 | // that method to return
52 | // It can be only called once
53 | func (mt *MockedTimer) StopValue(v bool) {
54 | mt.stopValue = v
55 | mt.stopMutex.Unlock()
56 | }
57 |
58 | // StoppedChan provides the channel that is closed when Stop() is called
59 | func (mt *MockedTimer) StoppedChan() chan struct{} {
60 | return mt.stopped
61 | }
62 |
63 | // Trigger for a usual Timer will send the provided time through timer.C(),
64 | // If this was a AfterFunc call, then trigger will ignore the time provided and will
65 | // call the scheduled function in the caller's goroutine
66 | func (mt *MockedTimer) Trigger(t time.Time) {
67 | select {
68 | case <-mt.stopped:
69 | panic("trying to tick on a stopped timer, does your test have a race condition?")
70 | default:
71 | if mt.c == nil {
72 | mt.f()
73 | } else {
74 | mt.c <- t
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/funcs.go:
--------------------------------------------------------------------------------
1 | // +build !timex_disable
2 |
3 | package timex
4 |
5 | import (
6 | "sync"
7 | "sync/atomic"
8 | "time"
9 | )
10 |
11 | // Now can be used as a replacement of time.Now()
12 | func Now() time.Time { return impl.Load().(Implementation).Now() }
13 |
14 | // Since can be used as a replacement of time.Since()
15 | func Since(t time.Time) time.Duration { return impl.Load().(Implementation).Since(t) }
16 |
17 | // Until can be used as a replacement of time.Until()
18 | func Until(t time.Time) time.Duration { return impl.Load().(Implementation).Until(t) }
19 |
20 | // Sleep can be used as a replacement of time.Sleep()
21 | func Sleep(d time.Duration) { impl.Load().(Implementation).Sleep(d) }
22 |
23 | // After can be used a replacement of time.After()
24 | func After(d time.Duration) <-chan time.Time { return impl.Load().(Implementation).After(d) }
25 |
26 | // AfterFunc can be used as a replacement of time.AfterFunc()
27 | func AfterFunc(d time.Duration, f func()) Timer { return impl.Load().(Implementation).AfterFunc(d, f) }
28 |
29 | // NewTicker creates a new ticker as a replacement of time.NewTicker
30 | func NewTicker(d time.Duration) Ticker { return impl.Load().(Implementation).NewTicker(d) }
31 |
32 | // NewTimer creates a new timer as a replacement of time.NewTimer
33 | func NewTimer(d time.Duration) Timer { return impl.Load().(Implementation).NewTimer(d) }
34 |
35 | // overridden is a lock we take when we override the timex implementation
36 | // since golang packages can run tests in different packages concurrently,
37 | // we want to make sure that there are no two implementations overriding
38 | // concurrently
39 | // https://medium.com/@xcoulon/how-to-avoid-parallel-execution-of-tests-in-golang-763d32d88eec
40 | var overridden sync.Mutex
41 |
42 | // Override replaces the global implementation used for timex
43 | // The returned function should be called to restore the default implementation
44 | func Override(implementation Implementation) func() {
45 | overridden.Lock()
46 | impl.Store(implValue{implementation})
47 | return func() {
48 | impl.Store(implValue{Default{}})
49 | overridden.Unlock()
50 | }
51 | }
52 |
53 | // impl stores the current implementation being used
54 | // we don't use the RWMutex to read the impl itself because atomic.Value is faster
55 | // in a _frequent read - unfrequent write_ scenario according to the docs
56 | // https://tip.golang.org/pkg/sync/atomic/#Value
57 | var impl atomic.Value
58 |
59 | type implValue struct{ Implementation }
60 |
61 | func init() {
62 | impl.Store(implValue{Default{}})
63 | }
64 |
--------------------------------------------------------------------------------
/funcs_disabled_test.go:
--------------------------------------------------------------------------------
1 | // +build timex_disable
2 |
3 | package timex_test
4 |
5 | import (
6 | "testing"
7 | "time"
8 |
9 | "github.com/cabify/timex"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func TestNow(t *testing.T) {
14 | diff := time.Since(timex.Now())
15 | assert.True(t, diff < time.Second)
16 | assert.True(t, diff > 0)
17 | }
18 |
19 | func TestSince(t *testing.T) {
20 | diff := timex.Since(time.Now())
21 | assert.True(t, diff < time.Second)
22 | assert.True(t, diff > 0)
23 | }
24 |
25 | func TestUntil(t *testing.T) {
26 | diff := timex.Until(time.Now())
27 | assert.True(t, diff < 0)
28 | assert.True(t, diff > -time.Second)
29 | }
30 |
31 | func TestAfterFunc(t *testing.T) {
32 | timeout := time.After(time.Second)
33 | ok := make(chan struct{})
34 |
35 | timex.AfterFunc(time.Millisecond, func() { close(ok) })
36 |
37 | select {
38 | case <-ok:
39 | // ok
40 | case <-timeout:
41 | t.Errorf("Timeout waiting for AfterFunc")
42 | }
43 | }
44 |
45 | func TestSleep(t *testing.T) {
46 | timeout := time.After(time.Second)
47 | ok := make(chan struct{})
48 |
49 | go func() {
50 | timex.Sleep(time.Millisecond)
51 | close(ok)
52 | }()
53 |
54 | select {
55 | case <-ok:
56 | // ok
57 | case <-timeout:
58 | t.Errorf("Timeout waiting for Sleep")
59 | }
60 | }
61 |
62 | func TestAfter(t *testing.T) {
63 | timeout := time.After(time.Second)
64 | ok := timex.After(time.Millisecond)
65 |
66 | select {
67 | case <-ok:
68 | // ok
69 | case <-timeout:
70 | t.Errorf("Timeout waiting for After")
71 | }
72 | }
73 |
74 | func TestNewTicker(t *testing.T) {
75 | timeout := time.After(time.Second)
76 | ticker := timex.NewTicker(100 * time.Millisecond)
77 |
78 | select {
79 | case <-ticker.C():
80 | // ok
81 | case <-timeout:
82 | t.Errorf("Timeout waiting for Ticker")
83 | }
84 |
85 | ticker.Stop()
86 |
87 | ok := timex.After(200 * time.Millisecond)
88 |
89 | select {
90 | case <-ticker.C():
91 | t.Errorf("Should not tick again since it's stopped")
92 | case <-ok:
93 | // ok
94 | }
95 | }
96 |
97 | func TestNewTimer(t *testing.T) {
98 | t.Run("tick", func(t *testing.T) {
99 | timeout := time.After(time.Second)
100 | timer := timex.NewTimer(100 * time.Millisecond)
101 |
102 | select {
103 | case <-timer.C():
104 | // ok
105 | case <-timeout:
106 | t.Errorf("Timeout waiting for Mock")
107 | }
108 | })
109 |
110 | t.Run("stop", func(t *testing.T) {
111 | timer := timex.NewTimer(100 * time.Millisecond)
112 | timer.Stop()
113 |
114 | ok := timex.After(200 * time.Millisecond)
115 |
116 | select {
117 | case <-timer.C():
118 | t.Errorf("Should not tick since it's stopped")
119 | case <-ok:
120 | // ok
121 | }
122 | })
123 | }
124 |
--------------------------------------------------------------------------------
/default_test.go:
--------------------------------------------------------------------------------
1 | package timex
2 |
3 | import (
4 | "testing"
5 | "time"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestDefault_Now(t *testing.T) {
11 | diff := time.Since(Default{}.Now())
12 | assert.True(t, diff < time.Second)
13 | assert.True(t, diff > 0)
14 | }
15 |
16 | func TestDefault_Since(t *testing.T) {
17 | diff := Default{}.Since(time.Now())
18 | assert.True(t, diff < time.Second)
19 | assert.True(t, diff > 0)
20 | }
21 |
22 | func TestDefault_Until(t *testing.T) {
23 | diff := Default{}.Until(time.Now())
24 | assert.True(t, diff < 0)
25 | assert.True(t, diff > -time.Second)
26 | }
27 |
28 | func TestDefault_AfterFunc(t *testing.T) {
29 | timeout := time.After(time.Second)
30 | ok := make(chan struct{})
31 |
32 | Default{}.AfterFunc(time.Millisecond, func() { close(ok) })
33 |
34 | select {
35 | case <-ok:
36 | // ok
37 | case <-timeout:
38 | t.Errorf("Timeout waiting for AfterFunc")
39 | }
40 | }
41 |
42 | func TestDefault_Sleep(t *testing.T) {
43 | timeout := time.After(time.Second)
44 | ok := make(chan struct{})
45 |
46 | go func() {
47 | Default{}.Sleep(time.Millisecond)
48 | close(ok)
49 | }()
50 |
51 | select {
52 | case <-ok:
53 | // ok
54 | case <-timeout:
55 | t.Errorf("Timeout waiting for Sleep")
56 | }
57 | }
58 |
59 | func TestDefault_After(t *testing.T) {
60 | timeout := time.After(time.Second)
61 | ok := Default{}.After(time.Millisecond)
62 |
63 | select {
64 | case <-ok:
65 | // ok
66 | case <-timeout:
67 | t.Errorf("Timeout waiting for After")
68 | }
69 | }
70 |
71 | func TestDefault_NewTicker(t *testing.T) {
72 | timeout := time.After(time.Second)
73 | ticker := Default{}.NewTicker(100 * time.Millisecond)
74 |
75 | select {
76 | case <-ticker.C():
77 | // ok
78 | case <-timeout:
79 | t.Errorf("Timeout waiting for Ticker")
80 | }
81 |
82 | ticker.Stop()
83 |
84 | ok := Default{}.After(200 * time.Millisecond)
85 |
86 | select {
87 | case <-ticker.C():
88 | t.Errorf("Should not tick again since it's stopped")
89 | case <-ok:
90 | // ok
91 | }
92 | }
93 |
94 | func TestDefault_NewTimer(t *testing.T) {
95 | t.Run("tick", func(t *testing.T) {
96 | timeout := time.After(time.Second)
97 | timer := Default{}.NewTimer(100 * time.Millisecond)
98 |
99 | select {
100 | case <-timer.C():
101 | // ok
102 | case <-timeout:
103 | t.Errorf("Timeout waiting for Mock")
104 | }
105 | })
106 |
107 | t.Run("stop", func(t *testing.T) {
108 | timer := Default{}.NewTimer(100 * time.Millisecond)
109 | timer.Stop()
110 |
111 | ok := After(200 * time.Millisecond)
112 |
113 | select {
114 | case <-timer.C():
115 | t.Errorf("Should not tick since it's stopped")
116 | case <-ok:
117 | // ok
118 | }
119 | })
120 | }
121 |
--------------------------------------------------------------------------------
/.golangci.yaml:
--------------------------------------------------------------------------------
1 | run:
2 | # timeout for analysis, e.g. 30s, 5m, default is 1m
3 | timeout: 1m
4 |
5 | # include test files or not, default is true
6 | tests: true
7 |
8 | # list of build tags, all linters use it. Default is empty list.
9 | build-tags: []
10 |
11 | # settings of specific linters
12 | linters-settings:
13 | govet:
14 | check-shadowing: false
15 | golint:
16 | min-confidence: 0
17 | gocyclo:
18 | min-complexity: 15
19 | maligned:
20 | suggest-new: true
21 | dupl:
22 | threshold: 100
23 | goconst:
24 | min-len: 2
25 | min-occurrences: 2
26 | misspell:
27 | locale: US
28 | gocritic:
29 | enabled-tags:
30 | - diagnostic
31 | - experimental
32 | - opinionated
33 | - style
34 | disabled-checks:
35 | - dupImport # https://github.com/go-critic/go-critic/issues/845
36 | - ifElseChain
37 | - octalLiteral
38 | - wrapperFunc
39 | funlen:
40 | lines: 20
41 | statements: 20
42 |
43 | linters:
44 | disable-all: true
45 | enable:
46 | - bodyclose
47 | - deadcode
48 | - depguard
49 | - dogsled
50 | - dupl
51 | - errcheck
52 | - funlen
53 | - goconst
54 | - gocritic
55 | - gocyclo
56 | - gofmt
57 | - goimports
58 | - golint
59 | - gosec
60 | - gosimple
61 | - govet
62 | - ineffassign
63 | - interfacer
64 | - lll
65 | - misspell
66 | - nakedret
67 | - scopelint
68 | - staticcheck
69 | - structcheck
70 | - stylecheck
71 | - typecheck
72 | - unconvert
73 | - unparam
74 | - unused
75 | - varcheck
76 | - whitespace
77 |
78 | # don't enable:
79 | # - gochecknoglobals
80 | # - gocognit
81 | # - godox
82 | # - maligned
83 | # - prealloc
84 | # - gochecknoinits
85 |
86 |
87 | issues:
88 | # Excluding configuration per-path, per-linter, per-text and per-source
89 | exclude-rules:
90 | # Exclude some linters from running on tests files.
91 | - path: _test\.go
92 | linters:
93 | - gocyclo
94 | - errcheck
95 | - dupl
96 | - gosec
97 | - scopelint
98 | - goconst
99 | - funlen
100 | - gocritic
101 |
102 | # Maximum issues count per one linter. Set to 0 to disable. Default is 50.
103 | max-issues-per-linter: 0
104 |
105 | # Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
106 | max-same-issues: 0
107 |
108 | # Show only new issues: if there are unstaged changes or untracked files,
109 | # only those changes are analyzed, else only changes in HEAD~ are analyzed.
110 | # It's a super-useful option for integration of golangci-lint into existing
111 | # large codebase. It's not practical to fix all existing issues at the moment
112 | # of integration: much better don't allow issues in new code.
113 | # Default is false.
114 | new: false
115 |
--------------------------------------------------------------------------------
/funcs_test.go:
--------------------------------------------------------------------------------
1 | // +build !timex_disable
2 |
3 | package timex_test
4 |
5 | import (
6 | "testing"
7 | "time"
8 |
9 | "github.com/cabify/timex"
10 | "github.com/cabify/timex/timexmock"
11 | "github.com/stretchr/testify/assert"
12 | "github.com/stretchr/testify/mock"
13 | )
14 |
15 | var (
16 | someDate = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
17 | someDuration = 288 * time.Second
18 | )
19 |
20 | func TestNow(t *testing.T) {
21 | timexmock.Mocked(func(mocked *timexmock.Implementation) {
22 | mocked.On("Now").Once().Return(someDate)
23 | defer mocked.AssertExpectations(t)
24 |
25 | now := timex.Now()
26 | assert.Equal(t, someDate, now)
27 | })
28 | }
29 |
30 | func TestSince(t *testing.T) {
31 | timexmock.Mocked(func(mocked *timexmock.Implementation) {
32 | mocked.On("Since", someDate).Once().Return(someDuration)
33 | defer mocked.AssertExpectations(t)
34 |
35 | since := timex.Since(someDate)
36 | assert.Equal(t, someDuration, since)
37 | })
38 | }
39 |
40 | func TestUntil(t *testing.T) {
41 | timexmock.Mocked(func(mocked *timexmock.Implementation) {
42 | mocked.On("Until", someDate).Once().Return(someDuration)
43 | defer mocked.AssertExpectations(t)
44 |
45 | until := timex.Until(someDate)
46 | assert.Equal(t, someDuration, until)
47 | })
48 | }
49 |
50 | func TestAfterFunc(t *testing.T) {
51 | timexmock.Mocked(func(mocked *timexmock.Implementation) {
52 | providedCorrectFunction := false
53 | expectedTimer := &timexmock.Timer{}
54 | mocked.On("AfterFunc", someDuration, mock.Anything).Once().Return(func(_ time.Duration, f func()) timex.Timer {
55 | f()
56 | return expectedTimer
57 | })
58 | defer mocked.AssertExpectations(t)
59 |
60 | timer := timex.AfterFunc(someDuration, func() { providedCorrectFunction = true })
61 | assert.Equal(t, expectedTimer, timer)
62 | assert.True(t, providedCorrectFunction)
63 | })
64 | }
65 |
66 | func TestSleep(t *testing.T) {
67 | timexmock.Mocked(func(mocked *timexmock.Implementation) {
68 | mocked.On("Sleep", someDuration).Once()
69 | defer mocked.AssertExpectations(t)
70 |
71 | timex.Sleep(someDuration)
72 | })
73 | }
74 |
75 | func TestAfter(t *testing.T) {
76 | timexmock.Mocked(func(mocked *timexmock.Implementation) {
77 | expectedChan := make(<-chan time.Time)
78 | mocked.On("After", someDuration).Once().Return(expectedChan)
79 | defer mocked.AssertExpectations(t)
80 |
81 | ch := timex.After(someDuration)
82 | assert.Equal(t, expectedChan, ch)
83 | })
84 | }
85 |
86 | func TestNewTicker(t *testing.T) {
87 | timexmock.Mocked(func(mocked *timexmock.Implementation) {
88 | expectedTicker := &timexmock.Ticker{}
89 | mocked.On("NewTicker", someDuration).Once().Return(expectedTicker)
90 | defer mocked.AssertExpectations(t)
91 |
92 | ticker := timex.NewTicker(someDuration)
93 | assert.Equal(t, expectedTicker, ticker)
94 | })
95 | }
96 |
97 | func TestNewTimer(t *testing.T) {
98 | timexmock.Mocked(func(mocked *timexmock.Implementation) {
99 | expectedTimer := &timexmock.Timer{}
100 | mocked.On("NewTimer", someDuration).Once().Return(expectedTimer)
101 | defer mocked.AssertExpectations(t)
102 |
103 | timer := timex.NewTimer(someDuration)
104 | assert.Equal(t, expectedTimer, timer)
105 | })
106 | }
107 |
--------------------------------------------------------------------------------
/timexmock/implementation.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v1.0.0. DO NOT EDIT.
2 |
3 | package timexmock
4 |
5 | import (
6 | time "time"
7 |
8 | mock "github.com/stretchr/testify/mock"
9 |
10 | timex "github.com/cabify/timex"
11 | )
12 |
13 | // Implementation is an autogenerated mock type for the Implementation type
14 | type Implementation struct {
15 | mock.Mock
16 | }
17 |
18 | // After provides a mock function with given fields: d
19 | func (_m *Implementation) After(d time.Duration) <-chan time.Time {
20 | ret := _m.Called(d)
21 |
22 | var r0 <-chan time.Time
23 | if rf, ok := ret.Get(0).(func(time.Duration) <-chan time.Time); ok {
24 | r0 = rf(d)
25 | } else {
26 | if ret.Get(0) != nil {
27 | r0 = ret.Get(0).(<-chan time.Time)
28 | }
29 | }
30 |
31 | return r0
32 | }
33 |
34 | // AfterFunc provides a mock function with given fields: d, f
35 | func (_m *Implementation) AfterFunc(d time.Duration, f func()) timex.Timer {
36 | ret := _m.Called(d, f)
37 |
38 | var r0 timex.Timer
39 | if rf, ok := ret.Get(0).(func(time.Duration, func()) timex.Timer); ok {
40 | r0 = rf(d, f)
41 | } else {
42 | if ret.Get(0) != nil {
43 | r0 = ret.Get(0).(timex.Timer)
44 | }
45 | }
46 |
47 | return r0
48 | }
49 |
50 | // NewTicker provides a mock function with given fields: d
51 | func (_m *Implementation) NewTicker(d time.Duration) timex.Ticker {
52 | ret := _m.Called(d)
53 |
54 | var r0 timex.Ticker
55 | if rf, ok := ret.Get(0).(func(time.Duration) timex.Ticker); ok {
56 | r0 = rf(d)
57 | } else {
58 | if ret.Get(0) != nil {
59 | r0 = ret.Get(0).(timex.Ticker)
60 | }
61 | }
62 |
63 | return r0
64 | }
65 |
66 | // NewTimer provides a mock function with given fields: d
67 | func (_m *Implementation) NewTimer(d time.Duration) timex.Timer {
68 | ret := _m.Called(d)
69 |
70 | var r0 timex.Timer
71 | if rf, ok := ret.Get(0).(func(time.Duration) timex.Timer); ok {
72 | r0 = rf(d)
73 | } else {
74 | if ret.Get(0) != nil {
75 | r0 = ret.Get(0).(timex.Timer)
76 | }
77 | }
78 |
79 | return r0
80 | }
81 |
82 | // Now provides a mock function with given fields:
83 | func (_m *Implementation) Now() time.Time {
84 | ret := _m.Called()
85 |
86 | var r0 time.Time
87 | if rf, ok := ret.Get(0).(func() time.Time); ok {
88 | r0 = rf()
89 | } else {
90 | r0 = ret.Get(0).(time.Time)
91 | }
92 |
93 | return r0
94 | }
95 |
96 | // Since provides a mock function with given fields: t
97 | func (_m *Implementation) Since(t time.Time) time.Duration {
98 | ret := _m.Called(t)
99 |
100 | var r0 time.Duration
101 | if rf, ok := ret.Get(0).(func(time.Time) time.Duration); ok {
102 | r0 = rf(t)
103 | } else {
104 | r0 = ret.Get(0).(time.Duration)
105 | }
106 |
107 | return r0
108 | }
109 |
110 | // Sleep provides a mock function with given fields: d
111 | func (_m *Implementation) Sleep(d time.Duration) {
112 | _m.Called(d)
113 | }
114 |
115 | // Until provides a mock function with given fields: t
116 | func (_m *Implementation) Until(t time.Time) time.Duration {
117 | ret := _m.Called(t)
118 |
119 | var r0 time.Duration
120 | if rf, ok := ret.Get(0).(func(time.Time) time.Duration); ok {
121 | r0 = rf(t)
122 | } else {
123 | r0 = ret.Get(0).(time.Duration)
124 | }
125 |
126 | return r0
127 | }
128 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # `timex`
2 |
3 | [](https://travis-ci.org/cabify/timex)
4 | [](https://coveralls.io/github/cabify/timex?branch=master)
5 | [](https://godoc.org/github.com/cabify/timex)
6 |
7 | `timex` is a test-friendly replacement for the `time` package.
8 |
9 | ## Usage
10 |
11 | Just replace your `time.Now()` by a `timex.Now()` call, etc.
12 |
13 | ## Mocking
14 |
15 | Use `timex.Override(...)` to replace the current implementation by another one, and use the function it returns to restore the default implementation. You can't override from several tests at the same time. You can use an auto-generated by [`mockery`][mockery] mock from `timexmock` package, or a controlled implementation from `timextest`.
16 |
17 | ### `timexmock`
18 |
19 | There's a `timexmock.Mocked(func(mocked *timexmock.Implementation) { ... })` wrapper that automatically creates a mock, sets it as the implementation to be used and defers a tear down to set the default implementation again.
20 |
21 | Example:
22 |
23 | ```go
24 | func TestSleep(t *testing.T) {
25 | timexmock.Mocked(func(mocked *timexmock.Implementation) {
26 | mocked.On("Sleep", someDuration).Once()
27 | defer mocked.AssertExpectations(t)
28 |
29 | timex.Sleep(someDuration)
30 | })
31 | }
32 | ```
33 |
34 | ### `timextest`
35 |
36 | Timextest provides a more complex API useful to control the behavior of concurrent programs, it is especially useful when the code interacts with timers like `time.Ticker`. Just like `timexmock`, `timextest` also provides a `timextest.Mocked(time.Time, func(*TestImplementation))` function to make mocking easier. Few examples can be found in [`timextest/example_test.go`](./timextest/example_test.go), this is one of them:
37 |
38 | ```go
39 | func ExampleTestImplementation_NewTicker() {
40 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
41 | go func() {
42 | ticker := timex.NewTicker(time.Hour)
43 | for t := range ticker.C() {
44 | fmt.Printf("%s\n", t)
45 | }
46 | }()
47 |
48 | tickerCall := <-mockedtimex.NewTickerCalls
49 | tickerCall.Mock.Tick(now.Add(time.Second))
50 | tickerCall.Mock.Tick(now.Add(2 * time.Second))
51 |
52 | // Output:
53 | // 2009-11-10 23:00:01 +0000 UTC
54 | // 2009-11-10 23:00:02 +0000 UTC
55 | })
56 | }
57 | ```
58 |
59 | ## Drawbacks
60 |
61 | ### Performance
62 |
63 | There's an obvious performance impact caused by the indirection of the call, it's actually 20-30% slower, however, in absolute numbers we're talking about 30 nanoseconds per call, so you probably should not worry about that. Notice that the difference is so small that it's not easy to get a stable result.
64 |
65 | ```
66 | $ go test -run=NONE -benchmem -benchtime=5s -bench=. .
67 | goos: darwin
68 | goarch: amd64
69 | pkg: github.com/cabify/timex
70 | BenchmarkTimeNow-4 49619665 112 ns/op 0 B/op 0 allocs/op
71 | BenchmarkTimexNow-4 41256012 145 ns/op 0 B/op 0 allocs/op
72 | ```
73 |
74 | If you're really worried about performance, you can disable part of the indirection by compiling with `timex_disable` tag, which will provide results similiar to the native implemenation calls:
75 |
76 | ```
77 | $ go test -run=NONE -benchmem -benchtime=5s -bench=. -tags=timex_disable .
78 | goos: darwin
79 | goarch: amd64
80 | pkg: github.com/cabify/timex
81 | BenchmarkTimeNow-4 49866967 116 ns/op 0 B/op 0 allocs/op
82 | BenchmarkTimexNow-4 47965780 109 ns/op 0 B/op 0 allocs/op
83 | ```
84 |
85 | ### Dogma
86 |
87 | Oh... yes, we're changing global variables and we'll obviously burn in hell, but if you're really into DI, you can also accept `timex.Implementation` interface as a dependency, and then inject either `timex.Default{}` or a testable implementation.
88 |
89 | [mockery]: https://github.com/vektra/mockery
90 |
--------------------------------------------------------------------------------
/timextest/test_implementation.go:
--------------------------------------------------------------------------------
1 | package timextest
2 |
3 | import (
4 | "sync"
5 | "time"
6 |
7 | "github.com/cabify/timex"
8 | )
9 |
10 | // Mock mocks/stubs the timex functions so they return constant known values
11 | // Also mocks the timex.AfterFunc, returning the calls made to it through a channel.
12 | // It mocks Sleep too: the delayed function will just unlock the caller of Sleep.
13 | func Mock(now time.Time) *TestImplementation {
14 | impl := &TestImplementation{
15 | SleepCalls: make(chan SleepCall),
16 | AfterCalls: make(chan AfterCall),
17 | AfterFuncCalls: make(chan AfterFuncCall),
18 | NewTickerCalls: make(chan NewTickerCall),
19 | NewTimerCalls: make(chan NewTimerCall),
20 |
21 | now: now,
22 | }
23 |
24 | impl.restore = timex.Override(impl)
25 |
26 | return impl
27 | }
28 |
29 | // Mocked mocks the current time and passes it to the provided function
30 | // Afterwards, it restores the default implementation
31 | func Mocked(now time.Time, f func(mocked *TestImplementation)) {
32 | mocked := Mock(now)
33 | defer mocked.TearDown()
34 | f(mocked)
35 | }
36 |
37 | // TestImplementation implements timex.Implementation for tests
38 | type TestImplementation struct {
39 | SleepCalls chan SleepCall
40 | AfterCalls chan AfterCall
41 | AfterFuncCalls chan AfterFuncCall
42 | NewTickerCalls chan NewTickerCall
43 | NewTimerCalls chan NewTimerCall
44 |
45 | sync.RWMutex
46 | now time.Time
47 |
48 | restore func()
49 | }
50 |
51 | // TearDown closes all the channels on the test implementation.
52 | // Useful to terminate goroutines watching those channels.
53 | func (ti *TestImplementation) TearDown() {
54 | close(ti.AfterFuncCalls)
55 | close(ti.AfterCalls)
56 | close(ti.NewTickerCalls)
57 | close(ti.NewTimerCalls)
58 | ti.RestoreDefaultImplementation()
59 | }
60 |
61 | // RestoreDefaultImplementation restores timex default implementation
62 | func (ti *TestImplementation) RestoreDefaultImplementation() {
63 | ti.restore()
64 | }
65 |
66 | // SetNow updates Now() value to a newer one
67 | func (ti *TestImplementation) SetNow(now time.Time) {
68 | ti.Lock()
69 | defer ti.Unlock()
70 | ti.now = now
71 | }
72 |
73 | // Now returns always the same now
74 | func (ti *TestImplementation) Now() time.Time {
75 | ti.RLock()
76 | defer ti.RUnlock()
77 | return ti.now
78 | }
79 |
80 | // Since returns the duration elapsed since mocked `now`
81 | func (ti *TestImplementation) Since(t time.Time) time.Duration {
82 | ti.RLock()
83 | defer ti.RUnlock()
84 | return ti.now.Sub(t)
85 | }
86 |
87 | // Until returns the duration until mocked `now`
88 | func (ti *TestImplementation) Until(t time.Time) time.Duration {
89 | ti.RLock()
90 | defer ti.RUnlock()
91 | return t.Sub(ti.now)
92 | }
93 |
94 | // Sleep sleeps until WakeUp is called
95 | func (ti *TestImplementation) Sleep(d time.Duration) {
96 | var wg sync.WaitGroup
97 | wg.Add(1)
98 | ti.SleepCalls <- SleepCall{Duration: d, WakeUp: wg.Done}
99 | wg.Wait()
100 | }
101 |
102 | // After returns a mocked timer
103 | func (ti *TestImplementation) After(d time.Duration) <-chan time.Time {
104 | timer := newMockedTimer()
105 | ti.AfterCalls <- AfterCall{Mock: timer, Duration: d}
106 | return timer.C()
107 | }
108 |
109 | // AfterFunc allows the AfterFunc mocking by pushing calls into ti.AfterFuncCalls
110 | func (ti *TestImplementation) AfterFunc(d time.Duration, f func()) timex.Timer {
111 | timer := newMockedFuncTimer(f)
112 | ti.AfterFuncCalls <- AfterFuncCall{Duration: d, Function: f, Mock: timer}
113 | return timer
114 | }
115 |
116 | // NewTicker returns a mocked ticker
117 | func (ti *TestImplementation) NewTicker(d time.Duration) timex.Ticker {
118 | ticker := newMockedTicker()
119 | ti.NewTickerCalls <- NewTickerCall{Duration: d, Mock: ticker}
120 | return ticker
121 | }
122 |
123 | // NewTimer returns a mocked timer
124 | func (ti *TestImplementation) NewTimer(d time.Duration) timex.Timer {
125 | timer := newMockedTimer()
126 | ti.NewTimerCalls <- NewTimerCall{Duration: d, Mock: timer}
127 | return timer
128 | }
129 |
130 | // AfterCall is a call to a mocked After
131 | // It relies on a MockedTimer which will never be stopped
132 | type AfterCall struct {
133 | Mock *MockedTimer
134 | Duration time.Duration
135 | }
136 |
137 | // NewTickerCall is a call to a mocked NewTicker
138 | type NewTickerCall struct {
139 | Mock *MockedTicker
140 | Duration time.Duration
141 | }
142 |
143 | // NewTimerCall is a call to a mocked NewTimer
144 | type NewTimerCall struct {
145 | Mock *MockedTimer
146 | Duration time.Duration
147 | }
148 |
149 | // AfterFuncCall represents a call made to timex.AfterFunc
150 | type AfterFuncCall struct {
151 | // Mock provides the underlying timer. Calling Trigger() on it will execute the function provided
152 | // in the calling goroutine
153 | Mock *MockedTimer
154 | Duration time.Duration
155 | // Function is the function provided, probably useless
156 | Function func()
157 | }
158 |
159 | // SleepCall represents a call made to timex.Sleep
160 | type SleepCall struct {
161 | // WakeUp allows calling goroutine to continue execution
162 | WakeUp func()
163 | Duration time.Duration
164 | }
165 |
--------------------------------------------------------------------------------
/timextest/example_test.go:
--------------------------------------------------------------------------------
1 | package timextest_test
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | "sync/atomic"
7 | "time"
8 |
9 | "github.com/cabify/timex"
10 | "github.com/cabify/timex/timextest"
11 | )
12 |
13 | var now = time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC)
14 |
15 | func ExampleTestImplementation_SetNow() {
16 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
17 | mockedtimex.SetNow(time.Unix(1, 0).UTC())
18 | fmt.Println(timex.Now())
19 | // Output:
20 | // 1970-01-01 00:00:01 +0000 UTC
21 | })
22 | }
23 |
24 | func ExampleTestImplementation_Now() {
25 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
26 | fmt.Println(timex.Now())
27 | // Output:
28 | // 2009-11-10 23:00:00 +0000 UTC
29 | })
30 | }
31 |
32 | func ExampleTestImplementation_Since() {
33 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
34 | fmt.Println(timex.Since(now.Add(-time.Hour)))
35 | // Output:
36 | // 1h0m0s
37 | })
38 | }
39 |
40 | func ExampleTestImplementation_Until() {
41 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
42 | fmt.Println(timex.Until(now.Add(time.Minute)))
43 | // Output:
44 | // 1m0s
45 | })
46 | }
47 |
48 | // ExampleTestImplementation_Sleep observes the execution of a tempSwitch that uses timex.Sleep
49 | // To change the state of the program just temporarily
50 | // This way the assertions are deterministic and we don't have to wait the real amount of time
51 | func ExampleTestImplementation_Sleep() {
52 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
53 | sw := tempSwitch{new(int64)}
54 | done := make(chan struct{})
55 |
56 | // Check it's turned off
57 | fmt.Printf("First state of switch is %t\n", sw.IsTurnedOn())
58 |
59 | go func() {
60 | sw.TurnOn()
61 | close(done)
62 | }()
63 |
64 | // Wait until the program is sleeping
65 | sleepCall := <-mockedtimex.SleepCalls
66 |
67 | // Check it's turned on
68 | fmt.Printf("Then it's temporarily %t\n", sw.IsTurnedOn())
69 |
70 | // Let it wake up and wait until TurnOn call finishes execution
71 | sleepCall.WakeUp()
72 | <-done
73 |
74 | // Check it's turned off again
75 | fmt.Printf("And then it's %t again\n", sw.IsTurnedOn())
76 |
77 | // Output:
78 | // First state of switch is false
79 | // Then it's temporarily true
80 | // And then it's false again
81 | })
82 | }
83 |
84 | func ExampleTestImplementation_After() {
85 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
86 | var value bool
87 |
88 | wg := sync.WaitGroup{}
89 | wg.Add(1)
90 | go func() {
91 | defer wg.Done()
92 | <-timex.After(time.Minute)
93 | value = false
94 | }()
95 |
96 | // Note that the race detector would usually complain about this read because it would be
97 | // racy with the test
98 | value = true
99 |
100 | (<-mockedtimex.AfterCalls).Mock.Trigger(time.Time{})
101 | wg.Wait()
102 |
103 | fmt.Println(value)
104 |
105 | // Output:
106 | // false
107 | })
108 | }
109 |
110 | func ExampleTestImplementation_AfterFunc() {
111 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
112 | go func() {
113 | timex.AfterFunc(time.Second, func() { fmt.Println("This happens a second later") })
114 | timex.AfterFunc(time.Hour, func() { fmt.Println("This happens an hour later") })
115 | }()
116 |
117 | firstCall := <-mockedtimex.AfterFuncCalls
118 | fmt.Printf("First function is scheduled for %s\n", firstCall.Duration)
119 |
120 | secondCall := <-mockedtimex.AfterFuncCalls
121 | fmt.Printf("Second function is scheduled for %s\n", secondCall.Duration)
122 |
123 | firstCall.Mock.Trigger(now)
124 | secondCall.Mock.Trigger(now)
125 |
126 | // Output:
127 | // First function is scheduled for 1s
128 | // Second function is scheduled for 1h0m0s
129 | // This happens a second later
130 | // This happens an hour later
131 | })
132 | }
133 |
134 | func ExampleTestImplementation_NewTicker() {
135 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
136 | go func() {
137 | ticker := timex.NewTicker(time.Hour)
138 | for t := range ticker.C() {
139 | fmt.Printf("%s\n", t)
140 | }
141 | }()
142 |
143 | tickerCall := <-mockedtimex.NewTickerCalls
144 | tickerCall.Mock.Tick(now.Add(time.Second))
145 | tickerCall.Mock.Tick(now.Add(2 * time.Second))
146 |
147 | // Output:
148 | // 2009-11-10 23:00:01 +0000 UTC
149 | // 2009-11-10 23:00:02 +0000 UTC
150 | })
151 | }
152 |
153 | func ExampleTestImplementation_NewTimer() {
154 | timextest.Mocked(now, func(mockedtimex *timextest.TestImplementation) {
155 | wg := sync.WaitGroup{}
156 | wg.Add(1)
157 | go func(d time.Duration) {
158 | defer wg.Done()
159 | ticker := timex.NewTimer(d)
160 | t := <-ticker.C()
161 | fmt.Println(t)
162 | }(time.Minute)
163 |
164 | timer := <-mockedtimex.NewTimerCalls
165 | timer.Mock.Trigger(now.Add(timer.Duration))
166 | wg.Wait()
167 |
168 | // Output:
169 | // 2009-11-10 23:01:00 +0000 UTC
170 | })
171 | }
172 |
173 | type tempSwitch struct{ val *int64 }
174 |
175 | func (ts tempSwitch) TurnOn() {
176 | atomic.AddInt64(ts.val, 1)
177 | timex.Sleep(time.Hour)
178 | atomic.AddInt64(ts.val, -1)
179 | }
180 |
181 | func (ts tempSwitch) IsTurnedOn() bool {
182 | return atomic.LoadInt64(ts.val) == 1
183 | }
184 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2018 Maxi Mobility Spain SL and contributors.
2 |
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 |
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 |
16 | -------------------------------------------------------------------------
17 | Apache License
18 | Version 2.0, January 2004
19 | http://www.apache.org/licenses/
20 |
21 |
22 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
23 |
24 | 1. Definitions.
25 |
26 | "License" shall mean the terms and conditions for use, reproduction,
27 | and distribution as defined by Sections 1 through 9 of this document.
28 |
29 | "Licensor" shall mean the copyright owner or entity authorized by
30 | the copyright owner that is granting the License.
31 |
32 | "Legal Entity" shall mean the union of the acting entity and all
33 | other entities that control, are controlled by, or are under common
34 | control with that entity. For the purposes of this definition,
35 | "control" means (i) the power, direct or indirect, to cause the
36 | direction or management of such entity, whether by contract or
37 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
38 | outstanding shares, or (iii) beneficial ownership of such entity.
39 |
40 | "You" (or "Your") shall mean an individual or Legal Entity
41 | exercising permissions granted by this License.
42 |
43 | "Source" form shall mean the preferred form for making modifications,
44 | including but not limited to software source code, documentation
45 | source, and configuration files.
46 |
47 | "Object" form shall mean any form resulting from mechanical
48 | transformation or translation of a Source form, including but
49 | not limited to compiled object code, generated documentation,
50 | and conversions to other media types.
51 |
52 | "Work" shall mean the work of authorship, whether in Source or
53 | Object form, made available under the License, as indicated by a
54 | copyright notice that is included in or attached to the work
55 | (an example is provided in the Appendix below).
56 |
57 | "Derivative Works" shall mean any work, whether in Source or Object
58 | form, that is based on (or derived from) the Work and for which the
59 | editorial revisions, annotations, elaborations, or other modifications
60 | represent, as a whole, an original work of authorship. For the purposes
61 | of this License, Derivative Works shall not include works that remain
62 | separable from, or merely link (or bind by name) to the interfaces of,
63 | the Work and Derivative Works thereof.
64 |
65 | "Contribution" shall mean any work of authorship, including
66 | the original version of the Work and any modifications or additions
67 | to that Work or Derivative Works thereof, that is intentionally
68 | submitted to Licensor for inclusion in the Work by the copyright owner
69 | or by an individual or Legal Entity authorized to submit on behalf of
70 | the copyright owner. For the purposes of this definition, "submitted"
71 | means any form of electronic, verbal, or written communication sent
72 | to the Licensor or its representatives, including but not limited to
73 | communication on electronic mailing lists, source code control systems,
74 | and issue tracking systems that are managed by, or on behalf of, the
75 | Licensor for the purpose of discussing and improving the Work, but
76 | excluding communication that is conspicuously marked or otherwise
77 | designated in writing by the copyright owner as "Not a Contribution."
78 |
79 | "Contributor" shall mean Licensor and any individual or Legal Entity
80 | on behalf of whom a Contribution has been received by Licensor and
81 | subsequently incorporated within the Work.
82 |
83 | 2. Grant of Copyright License. Subject to the terms and conditions of
84 | this License, each Contributor hereby grants to You a perpetual,
85 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
86 | copyright license to reproduce, prepare Derivative Works of,
87 | publicly display, publicly perform, sublicense, and distribute the
88 | Work and such Derivative Works in Source or Object form.
89 |
90 | 3. Grant of Patent License. Subject to the terms and conditions of
91 | this License, each Contributor hereby grants to You a perpetual,
92 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
93 | (except as stated in this section) patent license to make, have made,
94 | use, offer to sell, sell, import, and otherwise transfer the Work,
95 | where such license applies only to those patent claims licensable
96 | by such Contributor that are necessarily infringed by their
97 | Contribution(s) alone or by combination of their Contribution(s)
98 | with the Work to which such Contribution(s) was submitted. If You
99 | institute patent litigation against any entity (including a
100 | cross-claim or counterclaim in a lawsuit) alleging that the Work
101 | or a Contribution incorporated within the Work constitutes direct
102 | or contributory patent infringement, then any patent licenses
103 | granted to You under this License for that Work shall terminate
104 | as of the date such litigation is filed.
105 |
106 | 4. Redistribution. You may reproduce and distribute copies of the
107 | Work or Derivative Works thereof in any medium, with or without
108 | modifications, and in Source or Object form, provided that You
109 | meet the following conditions:
110 |
111 | (a) You must give any other recipients of the Work or
112 | Derivative Works a copy of this License; and
113 |
114 | (b) You must cause any modified files to carry prominent notices
115 | stating that You changed the files; and
116 |
117 | (c) You must retain, in the Source form of any Derivative Works
118 | that You distribute, all copyright, patent, trademark, and
119 | attribution notices from the Source form of the Work,
120 | excluding those notices that do not pertain to any part of
121 | the Derivative Works; and
122 |
123 | (d) If the Work includes a "NOTICE" text file as part of its
124 | distribution, then any Derivative Works that You distribute must
125 | include a readable copy of the attribution notices contained
126 | within such NOTICE file, excluding those notices that do not
127 | pertain to any part of the Derivative Works, in at least one
128 | of the following places: within a NOTICE text file distributed
129 | as part of the Derivative Works; within the Source form or
130 | documentation, if provided along with the Derivative Works; or,
131 | within a display generated by the Derivative Works, if and
132 | wherever such third-party notices normally appear. The contents
133 | of the NOTICE file are for informational purposes only and
134 | do not modify the License. You may add Your own attribution
135 | notices within Derivative Works that You distribute, alongside
136 | or as an addendum to the NOTICE text from the Work, provided
137 | that such additional attribution notices cannot be construed
138 | as modifying the License.
139 |
140 | You may add Your own copyright statement to Your modifications and
141 | may provide additional or different license terms and conditions
142 | for use, reproduction, or distribution of Your modifications, or
143 | for any such Derivative Works as a whole, provided Your use,
144 | reproduction, and distribution of the Work otherwise complies with
145 | the conditions stated in this License.
146 |
147 | 5. Submission of Contributions. Unless You explicitly state otherwise,
148 | any Contribution intentionally submitted for inclusion in the Work
149 | by You to the Licensor shall be under the terms and conditions of
150 | this License, without any additional terms or conditions.
151 | Notwithstanding the above, nothing herein shall supersede or modify
152 | the terms of any separate license agreement you may have executed
153 | with Licensor regarding such Contributions.
154 |
155 | 6. Trademarks. This License does not grant permission to use the trade
156 | names, trademarks, service marks, or product names of the Licensor,
157 | except as required for reasonable and customary use in describing the
158 | origin of the Work and reproducing the content of the NOTICE file.
159 |
160 | 7. Disclaimer of Warranty. Unless required by applicable law or
161 | agreed to in writing, Licensor provides the Work (and each
162 | Contributor provides its Contributions) on an "AS IS" BASIS,
163 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
164 | implied, including, without limitation, any warranties or conditions
165 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
166 | PARTICULAR PURPOSE. You are solely responsible for determining the
167 | appropriateness of using or redistributing the Work and assume any
168 | risks associated with Your exercise of permissions under this License.
169 |
170 | 8. Limitation of Liability. In no event and under no legal theory,
171 | whether in tort (including negligence), contract, or otherwise,
172 | unless required by applicable law (such as deliberate and grossly
173 | negligent acts) or agreed to in writing, shall any Contributor be
174 | liable to You for damages, including any direct, indirect, special,
175 | incidental, or consequential damages of any character arising as a
176 | result of this License or out of the use or inability to use the
177 | Work (including but not limited to damages for loss of goodwill,
178 | work stoppage, computer failure or malfunction, or any and all
179 | other commercial damages or losses), even if such Contributor
180 | has been advised of the possibility of such damages.
181 |
182 | 9. Accepting Warranty or Additional Liability. While redistributing
183 | the Work or Derivative Works thereof, You may choose to offer,
184 | and charge a fee for, acceptance of support, warranty, indemnity,
185 | or other liability obligations and/or rights consistent with this
186 | License. However, in accepting such obligations, You may act only
187 | on Your own behalf and on Your sole responsibility, not on behalf
188 | of any other Contributor, and only if You agree to indemnify,
189 | defend, and hold each Contributor harmless for any liability
190 | incurred by, or claims asserted against, such Contributor by reason
191 | of your accepting any such warranty or additional liability.
192 |
193 | END OF TERMS AND CONDITIONS
194 |
--------------------------------------------------------------------------------