Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions executors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1989,28 +1989,3 @@ func Test_Delete(t *testing.T) {
r.Equal(count, ctx)
})
}

func Test_Create_Timestamps_With_NowFunc(t *testing.T) {
if PDB == nil {
t.Skip("skipping integration tests")
}
transaction(func(tx *Connection) {
r := require.New(t)

originalNowFunc := nowFunc
// ensure the original function is restored
defer func() {
nowFunc = originalNowFunc
}()

fakeNow, _ := time.Parse(time.RFC3339, "2019-07-14T00:00:00Z")
SetNowFunc(func() time.Time { return fakeNow })

friend := Friend{FirstName: "Yester", LastName: "Day"}
err := tx.Create(&friend)
r.NoError(err)

r.Equal(fakeNow, friend.CreatedAt)
r.Equal(fakeNow, friend.UpdatedAt)
})
}
30 changes: 30 additions & 0 deletions model.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"fmt"
"reflect"
"regexp"
"runtime"
"strings"
"time"

Expand All @@ -15,8 +17,36 @@ import (

var nowFunc = time.Now

func ensuredCalledInsideInit() {
// If the call stack is deeper than 1024... we got bigger problems.
callers := [1024]uintptr{}
callersCount := runtime.Callers(0, callers[:])
frames := runtime.CallersFrames(callers[:callersCount])

// The `init()` function gets compiled to `path/to/pkg.init.0` where
// the final number varies.
re := regexp.MustCompile(`\.init\.\d+$`)

for {
frame, ok := frames.Next()
if !ok {
break
}
if re.Match([]byte(frame.Func.Name())) {
return
}
}

panic("caller init() not found in call stack, reached end of call stack")
}

// SetNowFunc allows an override of time.Now for customizing CreatedAt/UpdatedAt
func SetNowFunc(f func() time.Time) {
// From the Go spec:
// > the invocation of init functions—happens in a single goroutine, sequentially, one package at a time
// Since this function mutates a global variable, it should only be called inside `init()`.
ensuredCalledInsideInit()

nowFunc = f
}

Expand Down