Skip to content

Commit

Permalink
feat: add function Runtime to dig.CallbackInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
tchung1118 committed Jun 27, 2024
1 parent 11da7b7 commit 6075bd2
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 5 deletions.
6 changes: 6 additions & 0 deletions callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

package dig

import "time"

// CallbackInfo contains information about a provided function or decorator
// called by Dig, and is passed to a [Callback] registered with
// [WithProviderCallback] or [WithDecoratorCallback].
Expand All @@ -32,6 +34,10 @@ type CallbackInfo struct {
// function, if any. When used in conjunction with [RecoverFromPanics],
// this will be set to a [PanicError] when the function panics.
Error error

// Runtime contains the duration of time it took for the associated
// function to run.
Runtime time.Duration
}

// Callback is a function that can be registered with a provided function
Expand Down
6 changes: 4 additions & 2 deletions constructor.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,13 @@ func (n *constructorNode) Call(c containerStore) (err error) {
}

if n.callback != nil {
start := c.clock().Now()
// Wrap in separate func to include PanicErrors
defer func() {
n.callback(CallbackInfo{
Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name),
Error: err,
Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name),
Error: err,
Runtime: c.clock().Since(start),
})
}()
}
Expand Down
19 changes: 19 additions & 0 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"math/rand"
"reflect"

"go.uber.org/dig/internal/digclock"
"go.uber.org/dig/internal/dot"
)

Expand Down Expand Up @@ -141,6 +142,9 @@ type containerStore interface {

// Returns invokerFn function to use when calling arguments.
invoker() invokerFn

// Returns a clock to use
clock() digclock.Clock
}

// New constructs a Container.
Expand Down Expand Up @@ -211,6 +215,21 @@ func (o setRandOption) applyOption(c *Container) {
c.scope.rand = o.r
}

// Changes the source of time for the container.
func setClock(c digclock.Clock) Option {
return setClockOption{c: c}
}

type setClockOption struct{ c digclock.Clock }

func (o setClockOption) String() string {
return fmt.Sprintf("setClock(%v)", o.c)
}

func (o setClockOption) applyOption(c *Container) {
c.scope.clockSrc = o.c
}

// DryRun is an Option which, when set to true, disables invocation of functions supplied to
// Provide and Invoke. Use this to build no-op containers.
func DryRun(dry bool) Option {
Expand Down
6 changes: 4 additions & 2 deletions decorate.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,13 @@ func (n *decoratorNode) Call(s containerStore) (err error) {
}

if n.callback != nil {
start := s.clock().Now()
// Wrap in separate func to include PanicErrors
defer func() {
n.callback(CallbackInfo{
Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name),
Error: err,
Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name),
Error: err,
Runtime: s.clock().Since(start),
})
}()
}
Expand Down
10 changes: 9 additions & 1 deletion dig_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,16 @@

package dig

import "math/rand"
import (
"math/rand"

"go.uber.org/dig/internal/digclock"
)

func SetRand(r *rand.Rand) Option {
return setRand(r)
}

func SetClock(c digclock.Clock) Option {
return setClock(c)
}
50 changes: 50 additions & 0 deletions dig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/dig"
"go.uber.org/dig/internal/digclock"
"go.uber.org/dig/internal/digtest"
)

Expand Down Expand Up @@ -1796,6 +1797,55 @@ func TestCallback(t *testing.T) {
})
}

func TestCallbackRuntime(t *testing.T) {
t.Run("provided ctor runtime", func(t *testing.T) {
var called bool

mockClock := digclock.NewMock()
c := digtest.New(t, dig.SetClock(mockClock))
c.RequireProvide(
func() int {
mockClock.Add(1 * time.Millisecond)
return 5
},
dig.WithProviderCallback(func(ci dig.CallbackInfo) {
assert.Equal(t, "go.uber.org/dig_test.TestCallbackRuntime.func1.1", ci.Name)
assert.NoError(t, ci.Error)
assert.Equal(t, ci.Runtime, 1*time.Millisecond)

called = true
}),
)

c.Invoke(func(int) {})
assert.True(t, called)
})

t.Run("decorator runtime", func(t *testing.T) {
var called bool

mockClock := digclock.NewMock()
c := digtest.New(t, dig.SetClock(mockClock))
c.RequireProvide(giveInt)
c.RequireDecorate(
func(int) int {
mockClock.Add(1 * time.Millisecond)
return 10
},
dig.WithDecoratorCallback(func(ci dig.CallbackInfo) {
assert.Equal(t, "go.uber.org/dig_test.TestCallbackRuntime.func2.1", ci.Name)
assert.NoError(t, ci.Error)
assert.Equal(t, ci.Runtime, 1*time.Millisecond)

called = true
}),
)

c.Invoke(func(int) {})
assert.True(t, called)
})
}

func TestProvideConstructorErrors(t *testing.T) {
t.Run("multiple-type constructor returns multiple objects of same type", func(t *testing.T) {
c := digtest.New(t)
Expand Down
83 changes: 83 additions & 0 deletions internal/digclock/clock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) 2024 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package digclock

import (
"time"
)

// Clock defines how dig accesses time.
// We keep the interface pretty minimal.
type Clock interface {
Now() time.Time
Since(time.Time) time.Duration
}

// System is the default implementation of Clock based on real time.
var System Clock = systemClock{}

type systemClock struct{}

func (systemClock) Now() time.Time {
return time.Now()
}

func (systemClock) Since(t time.Time) time.Duration {
return time.Since(t)
}

// Mock is a fake source of time.
// It implements standard time operations, but allows
// the user to control the passage of time.
//
// Use the [Add] method to progress time.
//
// Note that this implementation is not safe for concurrent use.
type Mock struct {
now time.Time
}

var _ Clock = (*Mock)(nil)

// NewMock creates a new mock clock with the current time set to the current time.
func NewMock() *Mock {
return &Mock{now: time.Now()}
}

// Now returns the current time.
func (m *Mock) Now() time.Time {
return m.now
}

// Since returns the time elapsed since the given time.
func (m *Mock) Since(t time.Time) time.Duration {
return m.Now().Sub(t)
}

// Add progresses time by the given duration.
//
// It panics if the duration is negative.
func (m *Mock) Add(d time.Duration) {
if d < 0 {
panic("cannot add negative duration")
}
m.now = m.now.Add(d)
}
53 changes: 53 additions & 0 deletions internal/digclock/clock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) 2024 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package digclock

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestSystemClock(t *testing.T) {
clock := System
testClock(t, clock, func(d time.Duration) { time.Sleep(d) })
}

func TestMockClock(t *testing.T) {
clock := NewMock()
testClock(t, clock, clock.Add)
}

func testClock(t *testing.T, clock Clock, advance func(d time.Duration)) {
now := clock.Now()
assert.False(t, now.IsZero())

t.Run("Since", func(t *testing.T) {
advance(1 * time.Millisecond)
assert.NotZero(t, clock.Since(now), "time must have advanced")
})
}

func TestMock_AddNegative(t *testing.T) {
clock := NewMock()
assert.Panics(t, func() { clock.Add(-1) })
}
11 changes: 11 additions & 0 deletions scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"reflect"
"sort"
"time"

"go.uber.org/dig/internal/digclock"
)

// A ScopeOption modifies the default behavior of Scope; currently,
Expand Down Expand Up @@ -90,6 +92,9 @@ type Scope struct {

// All the child scopes of this Scope.
childScopes []*Scope

// clockSrc stores the source of time. Defaults to system clock.
clockSrc digclock.Clock
}

func newScope() *Scope {
Expand All @@ -102,6 +107,7 @@ func newScope() *Scope {
decoratedGroups: make(map[key]reflect.Value),
invokerFn: defaultInvoker,
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
clockSrc: digclock.System,
}
s.gh = newGraphHolder(s)
return s
Expand All @@ -117,6 +123,7 @@ func (s *Scope) Scope(name string, opts ...ScopeOption) *Scope {
child.name = name
child.parentScope = s
child.invokerFn = s.invokerFn
child.clockSrc = s.clockSrc
child.deferAcyclicVerification = s.deferAcyclicVerification
child.recoverFromPanics = s.recoverFromPanics

Expand Down Expand Up @@ -267,6 +274,10 @@ func (s *Scope) invoker() invokerFn {
return s.invokerFn
}

func (s *Scope) clock() digclock.Clock {
return s.clockSrc
}

// adds a new graphNode to this Scope and all of its descendent
// scope.
func (s *Scope) newGraphNode(wrapped interface{}, orders map[*Scope]int) {
Expand Down

0 comments on commit 6075bd2

Please sign in to comment.