From 11e74f6c5ef4283ab9cffdb6d0652b8fe9da2e15 Mon Sep 17 00:00:00 2001 From: nhatthm Date: Thu, 4 Sep 2025 07:57:09 +0200 Subject: [PATCH 1/3] Add step to set the next timestamp for the clock --- .github/workflows/lint.yaml | 8 +-- .github/workflows/test.yaml | 6 +-- .golangci.yaml | 88 ++++++++++++++++---------------- Makefile | 2 +- README.md | 2 +- clock.go | 40 +++++++++------ clock_test.go | 23 +++++++-- features/Clock.feature | 10 +++- features/bootstrap/godog_test.go | 6 ++- go.mod | 6 +-- go.sum | 11 ++-- godog.go | 26 ++++++---- godog_test.go | 11 ++++ 13 files changed, 148 insertions(+), 91 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index fc3b38b..9275ead 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -7,16 +7,16 @@ on: pull_request: env: - GO_VERSION: "1.21.x" + GO_VERSION: "1.25.x" jobs: lint: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: ${{ env.GO_VERSION }} @@ -25,7 +25,7 @@ jobs: make $GITHUB_OUTPUT - name: lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v8 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. version: ${{ steps.vars.outputs.GOLANGCI_LINT_VERSION }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7e63f49..5e5f79f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -20,15 +20,15 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Install Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Go cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: # In order: # * Module download cache diff --git a/.golangci.yaml b/.golangci.yaml index fcce7dc..ba0e894 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,57 +1,59 @@ -# See https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml +version: "2" run: tests: true - -linters-settings: - errcheck: - check-type-assertions: true - check-blank: true - gocyclo: - min-complexity: 20 - dupl: - threshold: 100 - misspell: - locale: US - unused: - check-exported: false - unparam: - check-exported: true - linters: - enable-all: true + default: all disable: - - deadcode - - exhaustivestruct + - copyloopvar + - depguard - exhaustruct - forbidigo - forcetypeassert - - gci - gochecknoglobals - - golint - - gomnd - - ifshort - - interfacer + - intrange - ireturn - lll - - maligned - - nosnakecase - - nolintlint # https://github.com/golangci/golangci-lint/issues/3063 + - mnd + - nolintlint - paralleltest - - scopelint - - structcheck - testpackage - - varcheck - varnamelen - wrapcheck - -issues: - exclude-use-default: false - exclude-rules: - - linters: - - dupl - - funlen - - goconst - - goerr113 - - gomnd - - noctx - path: "_test.go" + - wsl + settings: + dupl: + threshold: 100 + errcheck: + check-type-assertions: true + check-blank: true + gocyclo: + min-complexity: 20 + misspell: + locale: US + exclusions: + generated: lax + rules: + - linters: + - dupl + - err113 + - funlen + - goconst + - mnd + - noctx + - predeclared + path: _test.go + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index a834d0b..d303c53 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ MODULE_NAME=clocksteps VENDOR_DIR = vendor -GOLANGCI_LINT_VERSION ?= v1.52.2 +GOLANGCI_LINT_VERSION ?= v2.4.0 GO ?= go GOLANGCI_LINT ?= $(shell go env GOPATH)/bin/golangci-lint-$(GOLANGCI_LINT_VERSION) diff --git a/README.md b/README.md index 8066a40..6b20308 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ makes it easy to run tests with `time`. ## Prerequisites -- `Go >= 1.16` +- `Go >= 1.17` ## Usage diff --git a/clock.go b/clock.go index 01718d5..cd618cb 100644 --- a/clock.go +++ b/clock.go @@ -16,8 +16,8 @@ var _ clock.Clock = (*Clock)(nil) // Clock is a clock.Clock. type Clock struct { - timestamp *time.Time - mu sync.Mutex + timestamps []time.Time + mu sync.Mutex } // Now returns a fixed timestamp or time.Now(). @@ -25,11 +25,17 @@ func (c *Clock) Now() time.Time { c.mu.Lock() defer c.mu.Unlock() - if c.timestamp == nil { + if len(c.timestamps) == 0 { return time.Now() } - return *c.timestamp + result := c.timestamps[0] + + if len(c.timestamps) > 1 { + c.timestamps = c.timestamps[1:] + } + + return result } // Set fixes the clock at a time. @@ -37,7 +43,15 @@ func (c *Clock) Set(t time.Time) { c.mu.Lock() defer c.mu.Unlock() - c.timestamp = timestamp(t) + c.timestamps = []time.Time{t} +} + +// Next sets the next timestamps to be returned by Now(). +func (c *Clock) Next(t ...time.Time) { + c.mu.Lock() + defer c.mu.Unlock() + + c.timestamps = append(c.timestamps, t...) } // Add adds time to the clock. @@ -45,11 +59,11 @@ func (c *Clock) Add(d time.Duration) error { c.mu.Lock() defer c.mu.Unlock() - if c.timestamp == nil { + if len(c.timestamps) == 0 { return ErrClockIsNotSet } - c.timestamp = timestamp(c.timestamp.Add(d)) + c.timestamps[0] = c.timestamps[0].Add(d) return nil } @@ -59,11 +73,11 @@ func (c *Clock) AddDate(years, months, days int) error { c.mu.Lock() defer c.mu.Unlock() - if c.timestamp == nil { + if len(c.timestamps) == 0 { return ErrClockIsNotSet } - c.timestamp = timestamp(c.timestamp.AddDate(years, months, days)) + c.timestamps[0] = c.timestamps[0].AddDate(years, months, days) return nil } @@ -73,7 +87,7 @@ func (c *Clock) Freeze() { c.mu.Lock() defer c.mu.Unlock() - c.timestamp = timestamp(time.Now()) + c.timestamps = []time.Time{time.Now()} } // Unfreeze unfreezes the clock. @@ -81,7 +95,7 @@ func (c *Clock) Unfreeze() { c.mu.Lock() defer c.mu.Unlock() - c.timestamp = nil + c.timestamps = nil } // Clock provides clock.Clock. @@ -93,7 +107,3 @@ func (c *Clock) Clock() clock.Clock { func New() *Clock { return &Clock{} } - -func timestamp(t time.Time) *time.Time { - return &t -} diff --git a/clock_test.go b/clock_test.go index edffb99..b339bdf 100644 --- a/clock_test.go +++ b/clock_test.go @@ -4,8 +4,10 @@ import ( "testing" "time" - "github.com/godogx/clocksteps" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/godogx/clocksteps" ) func TestClock(t *testing.T) { @@ -18,8 +20,8 @@ func TestClock(t *testing.T) { assert.True(t, now.Before(c.Now())) // Errors while adding time to a live clock. - assert.Equal(t, clocksteps.ErrClockIsNotSet, c.Add(time.Hour)) - assert.Equal(t, clocksteps.ErrClockIsNotSet, c.AddDate(0, 0, 1)) + require.ErrorIs(t, c.Add(time.Hour), clocksteps.ErrClockIsNotSet) + require.ErrorIs(t, c.AddDate(0, 0, 1), clocksteps.ErrClockIsNotSet) // Freeze the clock. c.Freeze() @@ -42,7 +44,7 @@ func TestClock(t *testing.T) { // Change the time. ts = ts.Add(2 * time.Hour) err := c.Add(2 * time.Hour) - assert.NoError(t, err) + require.NoError(t, err) <-time.After(50 * time.Millisecond) @@ -51,12 +53,23 @@ func TestClock(t *testing.T) { // Change the date. ts = ts.AddDate(2, 1, 3) err = c.AddDate(2, 1, 3) - assert.NoError(t, err) + require.NoError(t, err) <-time.After(50 * time.Millisecond) assert.Equal(t, ts, c.Now()) + // Add more timestamps. + ts2 := time.Date(2021, 2, 3, 4, 5, 6, 0, time.UTC) + c.Next(ts2) + + oldTs := c.Now() + + assert.Equal(t, ts, oldTs) + assert.NotEqual(t, ts2, oldTs) + assert.Equal(t, ts2, c.Now()) + assert.Equal(t, ts2, c.Now()) + // Unfreeze the clock. c.Unfreeze() diff --git a/features/Clock.feature b/features/Clock.feature index 66a0567..7f48f85 100644 --- a/features/Clock.feature +++ b/features/Clock.feature @@ -1,6 +1,6 @@ Feature: Without Background - Scenario: Set time + Scenario: Set and Next time Given the clock is at "2020-01-02T03:04:05Z" Then the time is "2020-01-02T03:04:05Z" @@ -14,8 +14,16 @@ Feature: Without Background Then the time is "2020-03-04T05:06:07Z" Given now is "2020-04-05T06:07:08Z" + And the clock advances to "2021-01-02T03:04:05Z" + And the clock changes to "2023-02-03T04:05:06Z" + And the clock moves forward to "2022-12-31T23:59:59Z" Then the time is "2020-04-05T06:07:08Z" + Then the time is "2021-01-02T03:04:05Z" + Then the time is "2023-02-03T04:05:06Z" + Then the time is "2022-12-31T23:59:59Z" + Then the time is "2022-12-31T23:59:59Z" + Then the time is "2022-12-31T23:59:59Z" Scenario: Add time Given the clock is at "2020-01-02T03:04:05Z" diff --git a/features/bootstrap/godog_test.go b/features/bootstrap/godog_test.go index bf0f496..6a26193 100644 --- a/features/bootstrap/godog_test.go +++ b/features/bootstrap/godog_test.go @@ -2,6 +2,7 @@ package bootstrap import ( "bytes" + "errors" "flag" "fmt" "math/rand" @@ -13,6 +14,7 @@ import ( "github.com/cucumber/godog" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.nhat.io/timeparser" "github.com/godogx/clocksteps" @@ -64,7 +66,7 @@ func RunSuite(t *testing.T, path string, featureContext func(t *testing.T, ctx * var paths []string files, err := os.ReadDir(filepath.Clean(path)) - assert.NoError(t, err) + require.NoError(t, err) paths = make([]string, 0, len(files)) @@ -133,7 +135,7 @@ func isNotNow(c *clocksteps.Clock) error { max := now.Add(10 * time.Millisecond) if ts.After(min) && ts.Before(max) { - return fmt.Errorf("the time is now") + return errors.New("the time is now") } return nil diff --git a/go.mod b/go.mod index 5656f71..e37a4dd 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/godogx/clocksteps go 1.17 require ( - github.com/cucumber/godog v0.13.0 + github.com/cucumber/godog v0.15.1 github.com/stretchr/testify v1.8.4 go.nhat.io/clock v0.7.0 go.nhat.io/timeparser v0.3.0 @@ -15,9 +15,9 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect - github.com/hashicorp/go-memdb v1.3.4 // indirect + github.com/hashicorp/go-memdb v1.3.5 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9786f96..f9924c1 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cucumber/gherkin/go/v26 v26.2.0 h1:EgIjePLWiPeslwIWmNQ3XHcypPsWAHoMCz/YEBKP4GI= github.com/cucumber/gherkin/go/v26 v26.2.0/go.mod h1:t2GAPnB8maCT4lkHL99BDCVNzCh1d7dBhCLt150Nr/0= -github.com/cucumber/godog v0.13.0 h1:KvX9kNWmAJwp882HmObGOyBbNUP5SXQ+SDLNajsuV7A= -github.com/cucumber/godog v0.13.0/go.mod h1:FX3rzIDybWABU4kuIXLZ/qtqEe1Ac5RdXmqvACJOces= +github.com/cucumber/godog v0.15.1 h1:rb/6oHDdvVZKS66hrhpjFQFHjthFSrQBCOI1LwshNTI= +github.com/cucumber/godog v0.15.1/go.mod h1:qju+SQDewOljHuq9NSM66s0xEhogx0q30flfxL4WUk8= github.com/cucumber/messages/go/v21 v21.0.1 h1:wzA0LxwjlWQYZd32VTlAVDTkW6inOFmSM+RuOwHZiMI= github.com/cucumber/messages/go/v21 v21.0.1/go.mod h1:zheH/2HS9JLVFukdrsPWoPdmUtmYQAQPLk7w5vWsk5s= github.com/cucumber/messages/go/v22 v22.0.0/go.mod h1:aZipXTKc0JnjCsXrJnuZpWhtay93k7Rn3Dee7iyPJjs= @@ -16,8 +16,9 @@ github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-memdb v1.3.4 h1:XSL3NR682X/cVk2IeV0d70N4DZ9ljI885xAEU8IoK3c= github.com/hashicorp/go-memdb v1.3.4/go.mod h1:uBTr1oQbtuMgd1SSGoR8YV27eT3sBHbYiNm53bMpgSg= +github.com/hashicorp/go-memdb v1.3.5 h1:b3taDMxCBCBVgyRrS1AZVHO14ubMYZB++QpNhBg+Nyo= +github.com/hashicorp/go-memdb v1.3.5/go.mod h1:8IVKKBkVe+fxFgdFOYxzQQNjz+sWCyHCdIC/+5+Vy1Y= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -35,8 +36,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/godog.go b/godog.go index bf1a265..e59fa32 100644 --- a/godog.go +++ b/godog.go @@ -9,20 +9,13 @@ import ( "go.nhat.io/timeparser" ) -// RegisterContext registers clock to godog tests. -// -// Deprecated: use RegisterSteps instead. -func (c *Clock) RegisterContext(s *godog.ScenarioContext) { - c.RegisterSteps(s) -} - // RegisterSteps registers clock to godog tests. func (c *Clock) RegisterSteps(s *godog.ScenarioContext) { - s.After(func(context.Context, *godog.Scenario, error) (context.Context, error) { + s.After(func(ctx context.Context, _ *godog.Scenario, _ error) (context.Context, error) { // Unfreeze the clock. c.Unfreeze() - return nil, nil + return ctx, nil //nolint: nilnil }) s.Step(`(?:the )?clock is at "([^"]*)"`, c.set) @@ -30,6 +23,10 @@ func (c *Clock) RegisterSteps(s *godog.ScenarioContext) { s.Step(`sets? (?:the )?clock to "([^"]*)"`, c.set) s.Step(`now is "([^"]*)"`, c.set) + s.Step(`(?:the )?clock advances to "([^"]*)"`, c.next) + s.Step(`(?:the )?clock changes to "([^"]*)"`, c.next) + s.Step(`(?:the )?clock moves forward to "([^"]*)"`, c.next) + s.Step(`adds? ([^\s]*) to (?:the )?clock`, c.add) s.Step(`adds? ([0-9]+) days? to (?:the )?clock`, c.addDay) s.Step(`adds? ([0-9]+) months? to (?:the )?clock`, c.addMonth) @@ -54,6 +51,17 @@ func (c *Clock) set(t string) error { return nil } +func (c *Clock) next(t string) error { + ts, err := timeparser.Parse(t) + if err != nil { + return err + } + + c.Next(ts) + + return nil +} + func (c *Clock) add(s string) error { d, err := time.ParseDuration(s) if err != nil { diff --git a/godog_test.go b/godog_test.go index 4fe9bb5..2fc9efd 100644 --- a/godog_test.go +++ b/godog_test.go @@ -17,6 +17,17 @@ func TestClock_setError(t *testing.T) { assert.EqualError(t, err, expectedError) } +func TestClock_nextError(t *testing.T) { + t.Parallel() + + c := New() + + err := c.next("foobar") + expectedError := `parsing time "foobar" as "2006-01-02": cannot parse "foobar" as "2006"` + + assert.EqualError(t, err, expectedError) +} + func TestClock_addDateError(t *testing.T) { t.Parallel() From afaaa53630a7beb3bc8ea1e0536086a9e8bbca04 Mon Sep 17 00:00:00 2001 From: nhatthm Date: Thu, 4 Sep 2025 07:58:43 +0200 Subject: [PATCH 2/3] Add more go versions --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 5e5f79f..a2ef098 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,7 +16,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest, macos-latest ] - go-version: [ 1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x ] + go-version: [ 1.17.x, 1.18.x, 1.19.x, 1.20.x, 1.21.x, 1.22.x, 1.23.x, 1.24.x, 1.25.x ] runs-on: ${{ matrix.os }} steps: - name: Install Go From 1ce014640f0c2d941ce2c01e4d7b6ac5bcbdaede Mon Sep 17 00:00:00 2001 From: nhatthm Date: Thu, 4 Sep 2025 07:59:27 +0200 Subject: [PATCH 3/3] Fix lint issues --- features/Clock.feature | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/features/Clock.feature b/features/Clock.feature index 7f48f85..3274c44 100644 --- a/features/Clock.feature +++ b/features/Clock.feature @@ -19,11 +19,11 @@ Feature: Without Background And the clock moves forward to "2022-12-31T23:59:59Z" Then the time is "2020-04-05T06:07:08Z" - Then the time is "2021-01-02T03:04:05Z" - Then the time is "2023-02-03T04:05:06Z" - Then the time is "2022-12-31T23:59:59Z" - Then the time is "2022-12-31T23:59:59Z" - Then the time is "2022-12-31T23:59:59Z" + And the time is "2021-01-02T03:04:05Z" + And the time is "2023-02-03T04:05:06Z" + And the time is "2022-12-31T23:59:59Z" + And the time is "2022-12-31T23:59:59Z" + And the time is "2022-12-31T23:59:59Z" Scenario: Add time Given the clock is at "2020-01-02T03:04:05Z"