├── version.txt ├── ctxtimecheck ├── testdata │ └── src │ │ ├── a │ │ ├── go.mod │ │ └── a.go │ │ └── b │ │ ├── go.mod │ │ └── b.go ├── cmd │ └── ctxtimecheck │ │ └── main.go ├── ctxtimecheck_test.go └── ctxtimecheck.go ├── .github ├── release.yml ├── dependabot.yml └── workflows │ └── tagpr.yml ├── internal └── now.go ├── ctxtime.go ├── go.mod ├── LICENSE ├── CHANGELOG.md ├── .tagpr ├── ctxtimetest ├── ctxtimetest.go └── ctxtimetest_test.go ├── README.md └── go.sum /version.txt: -------------------------------------------------------------------------------- 1 | v0.2.2 2 | -------------------------------------------------------------------------------- /ctxtimecheck/testdata/src/a/go.mod: -------------------------------------------------------------------------------- 1 | module a 2 | 3 | go 1.22.7 4 | -------------------------------------------------------------------------------- /ctxtimecheck/testdata/src/b/go.mod: -------------------------------------------------------------------------------- 1 | module b 2 | 3 | go 1.22.7 4 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - tagpr 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /ctxtimecheck/cmd/ctxtimecheck/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/newmo-oss/ctxtime/ctxtimecheck" 5 | "golang.org/x/tools/go/analysis/unitchecker" 6 | ) 7 | 8 | func main() { unitchecker.Main(ctxtimecheck.Analyzer) } 9 | -------------------------------------------------------------------------------- /internal/now.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // Now will be changed by functions of ctxtimetest package. 9 | var Now = DefaultNow 10 | 11 | // DefaultNow is default behavior of [ctxtime.Now]. 12 | func DefaultNow(_ context.Context) time.Time { 13 | return time.Now().In(time.UTC) 14 | } 15 | -------------------------------------------------------------------------------- /ctxtime.go: -------------------------------------------------------------------------------- 1 | package ctxtime 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/newmo-oss/ctxtime/internal" 8 | ) 9 | 10 | // Now returns the current local time in UTC. 11 | // In unit tests, the return value can be controled by functions of ctxtimetest package. 12 | func Now(ctx context.Context) time.Time { 13 | return internal.Now(ctx) 14 | } 15 | -------------------------------------------------------------------------------- /ctxtimecheck/testdata/src/a/a.go: -------------------------------------------------------------------------------- 1 | package a 2 | 3 | import ( 4 | "time" 5 | stdtime "time" 6 | ) 7 | 8 | func ng() { 9 | time.Now() // want `do not use time\.Now, use ctxtime\.Now` 10 | now := time.Now 11 | now() // want `do not use time\.Now, use ctxtime\.Now` 12 | func() { 13 | time.Now() // want `do not use time\.Now, use ctxtime\.Now` 14 | }() 15 | stdtime.Now() // want `do not use time\.Now, use ctxtime\.Now` 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/tagpr.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/tagpr.yml 2 | name: tagpr 3 | on: 4 | push: 5 | branches: ["main"] 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-24.04 9 | permissions: 10 | contents: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 14 | - uses: Songmu/tagpr@3dca11e7c0d68637ee212ddd35acc3d30a7403a4 # v1.5.0 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /ctxtimecheck/ctxtimecheck_test.go: -------------------------------------------------------------------------------- 1 | package ctxtimecheck_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gostaticanalysis/testutil" 7 | "github.com/newmo-oss/ctxtime/ctxtimecheck" 8 | "golang.org/x/tools/go/analysis/analysistest" 9 | ) 10 | 11 | // TestAnalyzer is a test for Analyzer. 12 | func TestAnalyzer(t *testing.T) { 13 | testdata := testutil.WithModules(t, analysistest.TestData(), nil) 14 | analysistest.Run(t, testdata, ctxtimecheck.Analyzer, "a") 15 | analysistest.Run(t, testdata, ctxtimecheck.Analyzer, "b") 16 | } 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/newmo-oss/ctxtime 2 | 3 | go 1.23.10 4 | 5 | require ( 6 | github.com/google/uuid v1.6.0 7 | github.com/gostaticanalysis/analysisutil v0.7.1 8 | github.com/gostaticanalysis/ssainspect v0.3.0 9 | github.com/gostaticanalysis/testutil v0.6.1 10 | github.com/newmo-oss/gotestingmock v0.1.2 11 | github.com/newmo-oss/testid v0.2.0 12 | golang.org/x/tools v0.34.0 13 | ) 14 | 15 | require ( 16 | github.com/gostaticanalysis/comment v1.5.0 // indirect 17 | github.com/hashicorp/go-version v1.7.0 // indirect 18 | github.com/otiai10/copy v1.14.1 // indirect 19 | github.com/otiai10/mint v1.6.3 // indirect 20 | github.com/tenntenn/modver v1.0.1 // indirect 21 | github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 // indirect 22 | golang.org/x/mod v0.25.0 // indirect 23 | golang.org/x/sync v0.15.0 // indirect 24 | golang.org/x/sys v0.33.0 // indirect 25 | golang.org/x/text v0.23.0 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 newmo, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ctxtimecheck/testdata/src/b/b.go: -------------------------------------------------------------------------------- 1 | package b 2 | 3 | import ( 4 | "time" 5 | stdtime "time" 6 | ) 7 | 8 | func ng() { 9 | time.Date(0, 0, 0, 0, 0, 0, 0, time.Local) // want `do not use time\.Date, use ctxtime\.Now and its receiver methods to calculate date` 10 | date := time.Date 11 | date(0, 0, 0, 0, 0, 0, 0, time.Local) // want `do not use time\.Date, use ctxtime\.Now and its receiver methods to calculate date` 12 | func() { 13 | time.Date(0, 0, 0, 0, 0, 0, 0, time.Local) // want `do not use time\.Date, use ctxtime\.Now and its receiver methods to calculate date` 14 | date := time.Date 15 | date(0, 0, 0, 0, 0, 0, 0, time.Local) // want `do not use time\.Date, use ctxtime\.Now and its receiver methods to calculate date` 16 | }() 17 | stdtime.Date(0, 0, 0, 0, 0, 0, 0, stdtime.Local) // want `do not use time\.Date, use ctxtime\.Now and its receiver methods to calculate date` 18 | 19 | _ = []struct { 20 | Time time.Time 21 | DateFunc func() time.Time 22 | }{ 23 | { 24 | Time: time.Date(0, 0, 0, 0, 0, 0, 0, time.Local), // want `do not use time\.Date, use ctxtime\.Now and its receiver methods to calculate date` 25 | DateFunc: func() time.Time { 26 | return time.Date(0, 0, 0, 0, 0, 0, 0, time.Local) // want `do not use time\.Date, use ctxtime\.Now and its receiver methods to calculate date` 27 | }, 28 | }, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v0.2.2](https://github.com/newmo-oss/ctxtime/compare/v0.2.1...v0.2.2) - 2025-06-17 4 | - Add dependabot by @tenntenn in https://github.com/newmo-oss/ctxtime/pull/11 5 | - Bump github.com/gostaticanalysis/testutil from 0.5.2 to 0.6.1 by @dependabot in https://github.com/newmo-oss/ctxtime/pull/13 6 | - Bump golang.org/x/tools from 0.31.0 to 0.33.0 by @dependabot in https://github.com/newmo-oss/ctxtime/pull/15 7 | - Update Go version to 1.23.10 by @tenntenn in https://github.com/newmo-oss/ctxtime/pull/16 8 | - Bump golang.org/x/tools from 0.33.0 to 0.34.0 by @dependabot in https://github.com/newmo-oss/ctxtime/pull/17 9 | 10 | ## [v0.2.1](https://github.com/newmo-oss/ctxtime/compare/v0.2.0...v0.2.1) - 2025-03-11 11 | - Update go version and dependencies by @tenntenn in https://github.com/newmo-oss/ctxtime/pull/9 12 | 13 | ## [v0.2.0](https://github.com/newmo-oss/ctxtime/compare/v0.1.0...v0.2.0) - 2025-01-08 14 | - ctxtimecheck: update ssainspect by @110y in https://github.com/newmo-oss/ctxtime/pull/4 15 | - Add tagpr by @tenntenn in https://github.com/newmo-oss/ctxtime/pull/5 16 | - Fix test name by @tenntenn in https://github.com/newmo-oss/ctxtime/pull/7 17 | - Use gotestingmock package by @tenntenn in https://github.com/newmo-oss/ctxtime/pull/8 18 | 19 | ## [v0.1.0](https://github.com/newmo-oss/ctxtime/commits/v0.1.0) - 2024-09-20 20 | - Allow zero time by @tenntenn in https://github.com/newmo-oss/ctxtime/pull/1 21 | - Add a linter for ctxtime by @tenntenn in https://github.com/newmo-oss/ctxtime/pull/2 22 | - Add descriptions of linter by @tenntenn in https://github.com/newmo-oss/ctxtime/pull/3 23 | -------------------------------------------------------------------------------- /ctxtimecheck/ctxtimecheck.go: -------------------------------------------------------------------------------- 1 | package ctxtimecheck 2 | 3 | import ( 4 | "go/types" 5 | 6 | "github.com/gostaticanalysis/analysisutil" 7 | "github.com/gostaticanalysis/ssainspect" 8 | "golang.org/x/tools/go/analysis" 9 | ) 10 | 11 | const doc = "ctxtimecheck finds calling time.Now instead of ctxtime.Now" 12 | 13 | // Analyzer finds calling time.Now instead of ctxtime.Now. 14 | var Analyzer = &analysis.Analyzer{ 15 | Name: "ctxtimecheck", 16 | Doc: doc, 17 | Run: run, 18 | Requires: []*analysis.Analyzer{ 19 | ssainspect.Analyzer, 20 | }, 21 | } 22 | 23 | func run(pass *analysis.Pass) (any, error) { 24 | timenow, _ := analysisutil.ObjectOf(pass, "time", "Now").(*types.Func) 25 | checknow(pass, timenow) 26 | 27 | timedate := analysisutil.ObjectOf(pass, "time", "Date").(*types.Func) 28 | checkdate(pass, timedate) 29 | 30 | return nil, nil 31 | } 32 | 33 | func checknow(pass *analysis.Pass, timenow *types.Func) { 34 | if timenow == nil { 35 | // skip 36 | return 37 | } 38 | 39 | for s := range pass.ResultOf[ssainspect.Analyzer].(*ssainspect.Inspector).All() { 40 | if analysisutil.Called(s.Instr, nil, timenow) { 41 | pass.Reportf(s.Instr.Pos(), "do not use %s, use ctxtime.Now", timenow.FullName()) 42 | } 43 | } 44 | } 45 | 46 | func checkdate(pass *analysis.Pass, timedate *types.Func) { 47 | if timedate == nil { 48 | // skip 49 | return 50 | } 51 | 52 | for s := range pass.ResultOf[ssainspect.Analyzer].(*ssainspect.Inspector).All() { 53 | if analysisutil.Called(s.Instr, nil, timedate) { 54 | pass.Reportf(s.Instr.Pos(), "do not use %s, use ctxtime.Now and its receiver methods to calculate date", timedate.FullName()) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.tagpr: -------------------------------------------------------------------------------- 1 | # config file for the tagpr in git config format 2 | # The tagpr generates the initial configuration, which you can rewrite to suit your environment. 3 | # CONFIGURATIONS: 4 | # tagpr.releaseBranch 5 | # Generally, it is "main." It is the branch for releases. The pcpr tracks this branch, 6 | # creates or updates a pull request as a release candidate, or tags when they are merged. 7 | # 8 | # tagpr.versionFile 9 | # Versioning file containing the semantic version needed to be updated at release. 10 | # It will be synchronized with the "git tag". 11 | # Often this is a meta-information file such as gemspec, setup.cfg, package.json, etc. 12 | # Sometimes the source code file, such as version.go or Bar.pm, is used. 13 | # If you do not want to use versioning files but only git tags, specify the "-" string here. 14 | # You can specify multiple version files by comma separated strings. 15 | # 16 | # tagpr.vPrefix 17 | # Flag whether or not v-prefix is added to semver when git tagging. (e.g. v1.2.3 if true) 18 | # This is only a tagging convention, not how it is described in the version file. 19 | # 20 | # tagpr.changelog (Optional) 21 | # Flag whether or not changelog is added or changed during the release. 22 | # 23 | # tagpr.command (Optional) 24 | # Command to change files just before release. 25 | # 26 | # tagpr.tmplate (Optional) 27 | # Pull request template in go template format 28 | # 29 | # tagpr.release (Optional) 30 | # GitHub Release creation behavior after tagging [true, draft, false] 31 | # If this value is not set, the release is to be created. 32 | [tagpr] 33 | vPrefix = true 34 | releaseBranch = main 35 | versionFile = version.txt 36 | -------------------------------------------------------------------------------- /ctxtimetest/ctxtimetest.go: -------------------------------------------------------------------------------- 1 | package ctxtimetest 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "testing" 7 | "time" 8 | 9 | "github.com/newmo-oss/ctxtime/internal" 10 | "github.com/newmo-oss/testid" 11 | ) 12 | 13 | var fixedNows sync.Map 14 | 15 | func init() { 16 | if testing.Testing() { 17 | internal.Now = nowForTest 18 | } 19 | } 20 | 21 | // SetFixedNow fixes the return value of ctxtime.Now. 22 | // The fixed current time is set each test id which get from [testid.FromContext]. 23 | // If any test id cannot obtain from the context, the test will be fail with t.Fatal. 24 | // The fixed current time will be remove by t.Cleanup. 25 | func SetFixedNow(t testing.TB, ctx context.Context, tm time.Time) { 26 | t.Helper() 27 | 28 | tid, ok := testid.FromContext(ctx) 29 | if !ok { 30 | t.Fatal("failed to get test ID from the context") 31 | } 32 | 33 | t.Cleanup(func() { 34 | fixedNows.Delete(tid) 35 | }) 36 | 37 | fixedNows.Store(tid, tm) 38 | } 39 | 40 | // UnsetFixedNow removes the fixed current time which was set by [SetFixedNow]. 41 | // If any test id cannot obtain from the context, the test will be fail with t.Fatal. 42 | func UnsetFixedNow(t testing.TB, ctx context.Context) { 43 | t.Helper() 44 | 45 | tid, ok := testid.FromContext(ctx) 46 | if !ok { 47 | t.Fatal("failed to get test ID from the context") 48 | } 49 | 50 | fixedNows.Delete(tid) 51 | } 52 | 53 | func loadFixedTime(ctx context.Context) (time.Time, bool) { 54 | tid, ok := testid.FromContext(ctx) 55 | if !ok { 56 | return time.Time{}, false 57 | } 58 | 59 | v, ok := fixedNows.Load(tid) 60 | if !ok { 61 | return time.Time{}, false 62 | } 63 | 64 | tm, ok := v.(time.Time) 65 | if !ok { 66 | return time.Time{}, false 67 | } 68 | 69 | return tm, true 70 | } 71 | 72 | func nowForTest(ctx context.Context) time.Time { 73 | tm, ok := loadFixedTime(ctx) 74 | if !ok { 75 | return internal.DefaultNow(ctx) 76 | } 77 | return tm 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ctxtime [![Go Reference](https://pkg.go.dev/badge/github.com/newmo-oss/ctxtimeo.svg)](https://pkg.go.dev/github.com/newmo-oss/ctxtime)[![Go Report Card](https://goreportcard.com/badge/github.com/newmo-oss/ctxtime)](https://goreportcard.com/report/github.com/newmo-oss/ctxtime) 2 | 3 | ctxtime provides testable time.Now. 4 | 5 | ## Usage 6 | 7 | By default, `ctxtime.Now(ctx)` returns the current time in UTC. 8 | ctxtimetest.SetFixedNow' can be used to set the return value of `ctxtime.Now(ctx)`. 9 | The return value will be assocciated the test id that can be obtained from the context. 10 | However, if `testing.Testing` returns false, `ctxtimetest.SetFixedNow` won't affect `ctxtime.Now`. 11 | 12 | ```go 13 | package a_test 14 | 15 | import ( 16 | "context" 17 | "testing" 18 | "time" 19 | 20 | "github.com/google/uuid" 21 | "github.com/newmo-oss/ctxtime" 22 | "github.com/newmo-oss/ctxtime/ctxtimetest" 23 | "github.com/newmo-oss/testid" 24 | ) 25 | 26 | func Test(t *testing.T) { 27 | tid := uuid.NewString() 28 | ctx := testid.WithValue(context.Background(), tid) 29 | now := ctxtime.Now(ctx) 30 | ctxtimetest.SetFixedNow(t, ctx, now) 31 | time.Sleep(10 * time.Second) 32 | now2 := ctxtime.Now(ctx) 33 | t.Log(now == now2) // true 34 | } 35 | ``` 36 | 37 | ## Linter 38 | 39 | ctxtimecheck is a linter that finds calls of `time.Now` in your code. 40 | 41 | You can install ctxtimecheck by go install. 42 | 43 | ```sh 44 | $ go install github.com/newmo-oss/ctxtime/ctxtimecheck/cmd/ctxtimecheck@latest 45 | ``` 46 | 47 | ctxtimecheck can be run with the go vet command. 48 | 49 | ```sh 50 | $ go vet -vettool=$(which ctxtimecheck) ./... 51 | ``` 52 | 53 | If you are already using [gostaticanalysis/called], it can be used instead of ctxtimecheck as follows. 54 | 55 | ```sh 56 | $ go vet -vettool=$(which called) -called.funcs="time.Now" ./... 57 | ``` 58 | 59 | ## License 60 | MIT 61 | 62 | [gostaticanalysis/called]: https://github.com/gostaticanalysis/called 63 | -------------------------------------------------------------------------------- /ctxtimetest/ctxtimetest_test.go: -------------------------------------------------------------------------------- 1 | package ctxtimetest_test 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/google/uuid" 9 | 10 | "github.com/newmo-oss/gotestingmock" 11 | "github.com/newmo-oss/testid" 12 | 13 | "github.com/newmo-oss/ctxtime" 14 | "github.com/newmo-oss/ctxtime/ctxtimetest" 15 | ) 16 | 17 | func TestSetFixedNow(t *testing.T) { 18 | t.Parallel() 19 | 20 | t.Run("before calling SetFixedNow", func(t *testing.T) { 21 | t.Parallel() 22 | 23 | ctx := testid.WithValue(context.Background(), uuid.New().String()) 24 | now1 := ctxtime.Now(ctx) 25 | time.Sleep(100 * time.Nanosecond) 26 | now2 := ctxtime.Now(ctx) 27 | if now1.IsZero() || now2.IsZero() || now1 == now2 || now1.After(now2) { 28 | t.Errorf("Now must return current time without calling SetFixedNow: %v %v", now1, now2) 29 | } 30 | }) 31 | 32 | t.Run("after calling SetFixedNow", func(t *testing.T) { 33 | t.Parallel() 34 | 35 | ctx := testid.WithValue(context.Background(), uuid.New().String()) 36 | now := ctxtime.Now(ctx) 37 | ctxtimetest.SetFixedNow(t, ctx, now) 38 | fixed := ctxtime.Now(ctx) 39 | if fixed != now { 40 | t.Errorf("ctxtime.Now must return the time which had been set by SetFixedNow: %v %v", fixed, now) 41 | } 42 | }) 43 | 44 | t.Run("after calling UnsetFixedNow", func(t *testing.T) { 45 | t.Parallel() 46 | 47 | ctx := testid.WithValue(context.Background(), uuid.New().String()) 48 | now := ctxtime.Now(ctx) 49 | ctxtimetest.SetFixedNow(t, ctx, now) 50 | ctxtimetest.UnsetFixedNow(t, ctx) 51 | got := ctxtime.Now(ctx) 52 | if now == got || now.After(got) { 53 | t.Errorf("ctxtime.Now must return current time after calling UnsetFixedNow: %v %v", got, now) 54 | } 55 | }) 56 | 57 | t.Run("different test ID", func(t *testing.T) { 58 | t.Parallel() 59 | 60 | ctx := testid.WithValue(context.Background(), uuid.New().String()) 61 | 62 | now1 := ctxtime.Now(ctx) 63 | time.Sleep(100 * time.Nanosecond) 64 | now2 := ctxtime.Now(ctx) 65 | 66 | ctxtimetest.SetFixedNow(t, ctx, now1) 67 | fixed1 := ctxtime.Now(ctx) 68 | 69 | // test IDを変更 70 | ctx = testid.WithValue(context.Background(), uuid.New().String()) 71 | got := ctxtime.Now(ctx) 72 | 73 | if got == fixed1 || got == now1 { 74 | t.Errorf("ctxtime.Now must return different time between diffrent test IDs: %v %v", got, fixed1) 75 | } 76 | 77 | ctxtimetest.SetFixedNow(t, ctx, now2) 78 | fixed2 := ctxtime.Now(ctx) 79 | if fixed2 == fixed1 || fixed2 != now2 { 80 | t.Errorf("ctxtime.Now must return different time between diffrent test IDs: %v %v", fixed2, fixed1) 81 | } 82 | }) 83 | 84 | t.Run("unset test ID", func(t *testing.T) { 85 | t.Parallel() 86 | 87 | got := gotestingmock.Run(func(tb *gotestingmock.TB) { 88 | ctx := context.Background() 89 | now := ctxtime.Now(ctx) 90 | ctxtimetest.SetFixedNow(tb, ctx, now) 91 | }) 92 | 93 | if !(got.Failed && got.Goexit) { 94 | t.Error("ctxtimetest.SetFixedNow must mark failed and exit goroutine of the test when the test id was not related to the context") 95 | } 96 | }) 97 | 98 | t.Run("unset test ID and not calling SetFixedNow", func(t *testing.T) { 99 | t.Parallel() 100 | 101 | ctx := context.Background() 102 | now1 := ctxtime.Now(ctx) 103 | time.Sleep(100 * time.Nanosecond) 104 | now2 := time.Now().In(time.UTC) 105 | if now1.IsZero() || now2.Before(now1) { 106 | t.Error("ctxtime.Now must return the current time in unit tests") 107 | } 108 | }) 109 | 110 | t.Run("set zero time", func(t *testing.T) { 111 | t.Parallel() 112 | 113 | ctx := testid.WithValue(context.Background(), uuid.New().String()) 114 | ctxtimetest.SetFixedNow(t, ctx, time.Time{}) 115 | fixed := ctxtime.Now(ctx) 116 | if !fixed.IsZero() { 117 | t.Errorf("SetFixedNow must allow zero time") 118 | } 119 | }) 120 | } 121 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 2 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 4 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 5 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 6 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 7 | github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= 8 | github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= 9 | github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= 10 | github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= 11 | github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= 12 | github.com/gostaticanalysis/ssainspect v0.3.0 h1:IUftUp6UNAsE/nDU0YQI/NIZp49/LaYnACEdXjny0lU= 13 | github.com/gostaticanalysis/ssainspect v0.3.0/go.mod h1:gIcyFqS5D8mwQyjanLrQFf+dCD9bevQZjjajuGmA3f0= 14 | github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= 15 | github.com/gostaticanalysis/testutil v0.6.1 h1:DeKCG96QlhtNAz+/z2jjO3gIHFV+lHEwELddAsLohxg= 16 | github.com/gostaticanalysis/testutil v0.6.1/go.mod h1:XfUs9IH5sPfXbPIq+kHR64fCpB6pBf5mYeaZQdaTBpw= 17 | github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 18 | github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= 19 | github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 20 | github.com/josharian/mapfs v0.0.0-20210615234106-095c008854e6 h1:c+ctPFdISggaSNCfU1IueNBAsqetJSvMcpQlT+0OVdY= 21 | github.com/josharian/mapfs v0.0.0-20210615234106-095c008854e6/go.mod h1:Rv/momJI8DgrWnBZip+SgagpcgORIZQE5SERlxNb8LY= 22 | github.com/josharian/txtarfs v0.0.0-20240408113805-5dc76b8fe6bf h1:ZWuoyLMwZvLJ6OHUhPq1sZHa37Pikt6DXkZPhhOBzEE= 23 | github.com/josharian/txtarfs v0.0.0-20240408113805-5dc76b8fe6bf/go.mod h1:UbC32ft9G/jG+sZI8wLbIBNIrYr7vp/yqMDa9SxVBNA= 24 | github.com/newmo-oss/gotestingmock v0.1.2 h1:4u0+4juIFH8mGm90BkQ+T4Jl2qKK2qd/Wzry79yVT3U= 25 | github.com/newmo-oss/gotestingmock v0.1.2/go.mod h1:/clfmHccDd3UnwZPrloKKm+w8rNfzenFTLhMzUh4wFI= 26 | github.com/newmo-oss/testid v0.2.0 h1:vzZ0bbR8cHGwwBUix0DQB8LUCMCpRlxfEUAnfuY/ifI= 27 | github.com/newmo-oss/testid v0.2.0/go.mod h1:T2pO3QMy/bcbzgKmknRHGm99oAmAE2MjG8kG+twHNpA= 28 | github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= 29 | github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= 30 | github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= 31 | github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= 32 | github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= 33 | github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= 34 | github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= 35 | github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= 36 | github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= 37 | github.com/tenntenn/golden v0.5.4 h1:laddoKuzbzGYVinsSZyEPavPh4muyKd2SMhJTKH3F3s= 38 | github.com/tenntenn/golden v0.5.4/go.mod h1:0xI/4lpoHR65AUTmd1RKR9S1Uv0JR3yR2Q1Ob2bKqQA= 39 | github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= 40 | github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= 41 | github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= 42 | github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= 43 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 44 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 45 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 46 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 47 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 48 | golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= 49 | golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 50 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 51 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 52 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 53 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 54 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 55 | golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= 56 | golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 57 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 58 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 59 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 60 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 61 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 62 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 63 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 64 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 65 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 66 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 67 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 68 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 69 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 70 | golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= 71 | golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= 72 | golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= 73 | golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= 74 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 75 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 76 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 77 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 78 | --------------------------------------------------------------------------------