From 2d7a27e5c37cd714d360351486f9784898c0b87b Mon Sep 17 00:00:00 2001 From: Sri Krishna Paritala Date: Tue, 30 Apr 2019 16:29:10 +0530 Subject: [PATCH 01/25] Initial support for sub graphs --- dig.go | 83 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/dig.go b/dig.go index 2607e7db..dcb1c714 100644 --- a/dig.go +++ b/dig.go @@ -229,6 +229,15 @@ type Container struct { // Defer acyclic check on provide until Invoke. deferAcyclicVerification bool + + // Name of the container + name string + + // Sub graphs of the container + children []*Container + + // Parent is the container that spawned this + parent *Container } // containerWriter provides write access to the Container's underlying data @@ -332,15 +341,25 @@ func setRand(r *rand.Rand) Option { } func (c *Container) knownTypes() []reflect.Type { - typeSet := make(map[reflect.Type]struct{}, len(c.providers)) - for k := range c.providers { - typeSet[k.t] = struct{}{} + getKnowTypes := func(c *Container) []reflect.Type { + typeSet := make(map[reflect.Type]struct{}, len(c.providers)) + for k := range c.providers { + typeSet[k.t] = struct{}{} + } + + types := make([]reflect.Type, 0, len(typeSet)) + for t := range typeSet { + types = append(types, t) + } + + return types } - types := make([]reflect.Type, 0, len(typeSet)) - for t := range typeSet { - types = append(types, t) + types := make([]reflect.Type, 0) + for _, c := range append(c.children, c) { + types = append(types, getKnowTypes(c)...) } + sort.Sort(byTypeName(types)) return types } @@ -366,11 +385,23 @@ func (c *Container) submitGroupedValue(name string, t reflect.Type, v reflect.Va } func (c *Container) getValueProviders(name string, t reflect.Type) []provider { - return c.getProviders(key{name: name, t: t}) + providers := c.getProviders(key{name: name, t: t}) + + for _, c := range c.children { + providers = append(providers, c.getValueProviders(name, t)...) + } + + return providers } func (c *Container) getGroupProviders(name string, t reflect.Type) []provider { - return c.getProviders(key{group: name, t: t}) + providers := c.getProviders(key{group: name, t: t}) + + for _, c := range c.children { + providers = append(providers, c.getGroupProviders(name, t)...) + } + + return providers } func (c *Container) getProviders(k key) []provider { @@ -382,6 +413,14 @@ func (c *Container) getProviders(k key) []provider { return providers } +func (c *Container) getRoot() *Container { + if c.parent == nil { + return c + } + + return c.parent.getRoot() +} + // Provide teaches the container how to build values of one or more types and // expresses their dependencies. // @@ -433,6 +472,7 @@ func (c *Container) Provide(constructor interface{}, opts ...ProvideOption) erro // The function may return an error to indicate failure. The error will be // returned to the caller as-is. func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error { + cp := c.getRoot() // run invoke on root to get access to all the graphs ftype := reflect.TypeOf(function) if ftype == nil { return errors.New("can't invoke an untyped nil") @@ -446,20 +486,20 @@ func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error { return err } - if err := shallowCheckDependencies(c, pl); err != nil { + if err := shallowCheckDependencies(cp, pl); err != nil { return errMissingDependencies{ Func: digreflect.InspectFunc(function), Reason: err, } } - if !c.isVerifiedAcyclic { - if err := c.verifyAcyclic(); err != nil { + if !cp.isVerifiedAcyclic { + if err := cp.verifyAcyclic(); err != nil { return err } } - args, err := pl.BuildList(c) + args, err := pl.BuildList(cp) if err != nil { return errArgumentsFailed{ Func: digreflect.InspectFunc(function), @@ -479,6 +519,25 @@ func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error { return nil } +func (c *Container) Child(name string) *Container { + if name == "" { + panic("dig: name should not be empty") + } + + child := &Container{ + providers: make(map[key][]*node), + values: make(map[key]reflect.Value), + groups: make(map[key][]reflect.Value), + rand: rand.New(rand.NewSource(time.Now().UnixNano())), + name: name, + parent: c, + } + + c.children = append(c.children, child) + + return child +} + func (c *Container) verifyAcyclic() error { visited := make(map[key]struct{}) for _, n := range c.nodes { From dceb62112645b4aa51249597e6bf2a8bdf3ab051 Mon Sep 17 00:00:00 2001 From: Sri Krishna Paritala Date: Tue, 30 Apr 2019 16:34:29 +0530 Subject: [PATCH 02/25] Comments for Container.Child --- dig.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dig.go b/dig.go index dcb1c714..064efa1d 100644 --- a/dig.go +++ b/dig.go @@ -519,6 +519,12 @@ func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error { return nil } +// Child returns a named child of this container. The child container has +// full access to the parent's types, and any types provided to the child +// will be made available to the parent. +// +// The name of the child is for observability purposes only. As such, it +// does not have to be unique across different children of the container. func (c *Container) Child(name string) *Container { if name == "" { panic("dig: name should not be empty") From 24f7ecdc9bb774d9d0e66e5aebb9563ee2fea974 Mon Sep 17 00:00:00 2001 From: Sri Krishna Paritala Date: Tue, 30 Apr 2019 17:13:21 +0530 Subject: [PATCH 03/25] Added Tests --- dig_test.go | 1024 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1024 insertions(+) diff --git a/dig_test.go b/dig_test.go index 82b519bd..a323f345 100644 --- a/dig_test.go +++ b/dig_test.go @@ -29,6 +29,7 @@ import ( "math/rand" "os" "reflect" + "strconv" "testing" "time" @@ -941,6 +942,1029 @@ func TestEndToEndSuccess(t *testing.T) { }) } +func TestChildren(t *testing.T) { + t.Parallel() + + t.Run("pointer constructor", func(t *testing.T) { + c := New() + + ch := c.Child("child") + var b *bytes.Buffer + require.NoError(t, ch.Provide(func() *bytes.Buffer { + b = &bytes.Buffer{} + return b + }), "provide failed") + require.NoError(t, c.Invoke(func(got *bytes.Buffer) { + require.NotNil(t, got, "invoke got nil buffer") + require.True(t, got == b, "invoke got wrong buffer") + }), "invoke failed") + }) + + t.Run("nil pointer constructor", func(t *testing.T) { + // Dig shouldn't forbid this - it's perfectly reasonable to explicitly + // provide a typed nil, since that's often a convenient way to supply a + // default no-op implementation. + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() *bytes.Buffer { return nil }), "provide failed") + require.NoError(t, c.Invoke(func(b *bytes.Buffer) { + require.Nil(t, b, "expected to get nil buffer") + }), "invoke failed") + }) + + t.Run("struct constructor", func(t *testing.T) { + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() bytes.Buffer { + var buf bytes.Buffer + buf.WriteString("foo") + return buf + }), "provide failed") + require.NoError(t, c.Invoke(func(b bytes.Buffer) { + // ensure we're getting back the buffer we put in + require.Equal(t, "foo", b.String(), "invoke got new buffer") + }), "invoke failed") + }) + + t.Run("slice constructor", func(t *testing.T) { + c := New() + + ch := c.Child("child") + b1 := &bytes.Buffer{} + b2 := &bytes.Buffer{} + require.NoError(t, ch.Provide(func() []*bytes.Buffer { + return []*bytes.Buffer{b1, b2} + }), "provide failed") + require.NoError(t, c.Invoke(func(bs []*bytes.Buffer) { + require.Equal(t, 2, len(bs), "invoke got unexpected number of buffers") + require.True(t, b1 == bs[0], "first item did not match") + require.True(t, b2 == bs[1], "second item did not match") + }), "invoke failed") + }) + + t.Run("array constructor", func(t *testing.T) { + c := New() + + ch := c.Child("child") + bufs := [1]*bytes.Buffer{{}} + require.NoError(t, ch.Provide(func() [1]*bytes.Buffer { return bufs }), "provide failed") + require.NoError(t, c.Invoke(func(bs [1]*bytes.Buffer) { + require.NotNil(t, bs[0], "invoke got new array") + }), "invoke failed") + }) + + t.Run("map constructor", func(t *testing.T) { + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() map[string]string { + return map[string]string{} + }), "provide failed") + require.NoError(t, c.Invoke(func(m map[string]string) { + require.NotNil(t, m, "invoke got zero value map") + }), "invoke failed") + }) + + t.Run("channel constructor", func(t *testing.T) { + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() chan int { + return make(chan int) + }), "provide failed") + require.NoError(t, c.Invoke(func(ch chan int) { + require.NotNil(t, ch, "invoke got nil chan") + }), "invoke failed") + }) + + t.Run("func constructor", func(t *testing.T) { + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() func(int) { + return func(int) {} + }), "provide failed") + require.NoError(t, c.Invoke(func(f func(int)) { + require.NotNil(t, f, "invoke got nil function pointer") + }), "invoke failed") + }) + + t.Run("interface constructor", func(t *testing.T) { + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() io.Writer { + return &bytes.Buffer{} + }), "provide failed") + require.NoError(t, c.Invoke(func(w io.Writer) { + require.NotNil(t, w, "invoke got nil interface") + }), "invoke failed") + }) + + t.Run("param", func(t *testing.T) { + c := New() + + ch := c.Child("child") + type contents string + type Args struct { + In + + Contents contents + } + + require.NoError(t, + ch.Provide(func(args Args) *bytes.Buffer { + require.NotEmpty(t, args.Contents, "contents must not be empty") + return bytes.NewBufferString(string(args.Contents)) + }), "provide constructor failed") + + require.NoError(t, + ch.Provide(func() contents { return "hello world" }), + "provide value failed") + + require.NoError(t, c.Invoke(func(buff *bytes.Buffer) { + out, err := ioutil.ReadAll(buff) + require.NoError(t, err, "read from buffer failed") + require.Equal(t, "hello world", string(out), "contents don't match") + })) + }) + + t.Run("invoke param", func(t *testing.T) { + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() *bytes.Buffer { + return new(bytes.Buffer) + }), "provide failed") + + type Args struct { + In + + *bytes.Buffer + } + + require.NoError(t, c.Invoke(func(args Args) { + require.NotNil(t, args.Buffer, "invoke got nil buffer") + })) + }) + + t.Run("param wrapper", func(t *testing.T) { + var ( + buff *bytes.Buffer + called bool + ) + + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() *bytes.Buffer { + require.False(t, called, "constructor must be called exactly once") + called = true + buff = new(bytes.Buffer) + return buff + }), "provide failed") + + type MyParam struct{ In } + + type Args struct { + MyParam + + Buffer *bytes.Buffer + } + + require.NoError(t, c.Invoke(func(args Args) { + require.True(t, called, "constructor must be called first") + require.NotNil(t, args.Buffer, "invoke got nil buffer") + require.True(t, args.Buffer == buff, "buffer must match constructor's return value") + })) + }) + + t.Run("param recurse", func(t *testing.T) { + type anotherParam struct { + In + + Buffer *bytes.Buffer + } + + type someParam struct { + In + + Buffer *bytes.Buffer + Another anotherParam + } + + var ( + buff *bytes.Buffer + called bool + ) + + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() *bytes.Buffer { + require.False(t, called, "constructor must be called exactly once") + called = true + buff = new(bytes.Buffer) + return buff + }), "provide must not fail") + + require.NoError(t, c.Invoke(func(p someParam) { + require.True(t, called, "constructor must be called first") + + require.NotNil(t, p.Buffer, "someParam.Buffer must not be nil") + require.NotNil(t, p.Another.Buffer, "anotherParam.Buffer must not be nil") + + require.True(t, p.Buffer == p.Another.Buffer, "buffers fields must match") + require.True(t, p.Buffer == buff, "buffer must match constructor's return value") + }), "invoke must not fail") + }) + + t.Run("multiple-type constructor", func(t *testing.T) { + c := New() + + ch := c.Child("child") + constructor := func() (*bytes.Buffer, []int, error) { + return &bytes.Buffer{}, []int{42}, nil + } + consumer := func(b *bytes.Buffer, nums []int) { + assert.NotNil(t, b, "invoke got nil buffer") + assert.Equal(t, 1, len(nums), "invoke got empty slice") + } + require.NoError(t, ch.Provide(constructor), "provide failed") + require.NoError(t, c.Invoke(consumer), "invoke failed") + }) + + t.Run("multiple-type constructor is called once", func(t *testing.T) { + c := New() + + ch := c.Child("child") + type A struct{} + type B struct{} + count := 0 + constructor := func() (*A, *B, error) { + count++ + return &A{}, &B{}, nil + } + getA := func(a *A) { + assert.NotNil(t, a, "got nil A") + } + getB := func(b *B) { + assert.NotNil(t, b, "got nil B") + } + require.NoError(t, ch.Provide(constructor), "provide failed") + require.NoError(t, c.Invoke(getA), "A invoke failed") + require.NoError(t, c.Invoke(getB), "B invoke failed") + require.NoError(t, c.Invoke(func(a *A, b *B) {}), "AB invoke failed") + require.Equal(t, 1, count, "Constructor must be called once") + }) + + t.Run("method invocation inside Invoke", func(t *testing.T) { + c := New() + + ch := c.Child("child") + type A struct{} + type B struct{} + cA := func() (*A, error) { + return &A{}, nil + } + cB := func() (*B, error) { + return &B{}, nil + } + getA := func(a *A) { + c.Invoke(func(b *B) { + assert.NotNil(t, b, "got nil B") + }) + assert.NotNil(t, a, "got nil A") + } + + require.NoError(t, ch.Provide(cA), "provide failed") + require.NoError(t, ch.Provide(cB), "provide failed") + require.NoError(t, c.Invoke(getA), "A invoke failed") + }) + + t.Run("collections and instances of same type", func(t *testing.T) { + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() []*bytes.Buffer { + return []*bytes.Buffer{{}} + }), "providing collection failed") + require.NoError(t, ch.Provide(func() *bytes.Buffer { + return &bytes.Buffer{} + }), "providing pointer failed") + }) + + t.Run("optional param field", func(t *testing.T) { + type type1 struct{} + type type2 struct{} + type type3 struct{} + type type4 struct{} + type type5 struct{} + constructor := func() (*type1, *type3, *type4) { + return &type1{}, &type3{}, &type4{} + } + + c := New() + + ch := c.Child("child") + type param struct { + In + + T1 *type1 // regular 'ol type + T2 *type2 `optional:"true" useless_tag:"false"` // optional type NOT in the graph + T3 *type3 `unrelated:"foo=42, optional"` // type in the graph with unrelated tag + T4 *type4 `optional:"true"` // optional type present in the graph + T5 *type5 `optional:"t"` // optional type NOT in the graph with "yes" + } + require.NoError(t, ch.Provide(constructor)) + require.NoError(t, c.Invoke(func(p param) { + require.NotNil(t, p.T1, "whole param struct should not be nil") + assert.Nil(t, p.T2, "optional type not in the graph should return nil") + assert.NotNil(t, p.T3, "required type with unrelated tag not in the graph") + assert.NotNil(t, p.T4, "optional type in the graph should not return nil") + assert.Nil(t, p.T5, "optional type not in the graph should return nil") + })) + }) + + t.Run("out type inserts multiple objects into the graph", func(t *testing.T) { + type A struct{ name string } + type B struct{ name string } + type Ret struct { + Out + A // value type A + *B // pointer type *B + } + myA := A{"string A"} + myB := &B{"string B"} + + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() Ret { + return Ret{A: myA, B: myB} + }), "provide for the Ret struct should succeed") + require.NoError(t, c.Invoke(func(a A, b *B) { + assert.Equal(t, a.name, "string A", "value type should work for dig.Out") + assert.Equal(t, b.name, "string B", "pointer should work for dig.Out") + assert.True(t, myA == a, "should get the same pointer for &A") + assert.Equal(t, b, myB, "b and myB should be uqual") + })) + }) + + t.Run("constructor with optional", func(t *testing.T) { + type type1 struct{} + type type2 struct{} + + type param struct { + In + + T1 *type1 `optional:"true"` + } + + c := New() + + ch := c.Child("child") + + var gave *type2 + require.NoError(t, ch.Provide(func(p param) *type2 { + require.Nil(t, p.T1, "T1 must be nil") + gave = &type2{} + return gave + }), "provide failed") + + require.NoError(t, c.Invoke(func(got *type2) { + require.True(t, got == gave, "type2 reference must be the same") + }), "invoke failed") + }) + + t.Run("nested dependencies", func(t *testing.T) { + c := New() + + ch := c.Child("child") + + type A struct{ name string } + type B struct{ name string } + type C struct{ name string } + + require.NoError(t, ch.Provide(func() A { return A{"->A"} })) + require.NoError(t, ch.Provide(func(A) B { return B{"A->B"} })) + require.NoError(t, ch.Provide(func(A, B) C { return C{"AB->C"} })) + require.NoError(t, c.Invoke(func(a A, b B, c C) { + assert.Equal(t, a, A{"->A"}) + assert.Equal(t, b, B{"A->B"}) + assert.Equal(t, c, C{"AB->C"}) + }), "invoking should succeed") + }) + + t.Run("primitives", func(t *testing.T) { + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() string { return "piper" }), "string provide failed") + require.NoError(t, ch.Provide(func() int { return 42 }), "int provide failed") + require.NoError(t, ch.Provide(func() int64 { return 24 }), "int provide failed") + require.NoError(t, ch.Provide(func() time.Duration { + return 10 * time.Second + }), "time.Duration provide failed") + require.NoError(t, c.Invoke(func(i64 int64, i int, s string, d time.Duration) { + assert.Equal(t, 42, i) + assert.Equal(t, int64(24), i64) + assert.Equal(t, "piper", s) + assert.Equal(t, 10*time.Second, d) + })) + }) + + t.Run("out types recurse", func(t *testing.T) { + type A struct{} + type B struct{} + type C struct{} + // Contains A + type Ret1 struct { + Out + *A + } + // Contains *A (through Ret1), *B and C + type Ret2 struct { + Ret1 + *B + C + } + c := New() + + ch := c.Child("child") + + require.NoError(t, ch.Provide(func() Ret2 { + return Ret2{ + Ret1: Ret1{ + A: &A{}, + }, + B: &B{}, + C: C{}, + } + }), "provide for the Ret struct should succeed") + require.NoError(t, c.Invoke(func(a *A, b *B, c C) { + require.NotNil(t, a, "*A should be part of the container through Ret2->Ret1") + })) + }) + + t.Run("named instances can be created with tags", func(t *testing.T) { + c := New() + + ch := c.Child("child") + type A struct{ idx int } + + // returns three named instances of A + type ret struct { + Out + + A1 A `name:"first"` + A2 A `name:"second"` + A3 A `name:"third"` + } + + // requires two specific named instances + type param struct { + In + + A1 A `name:"first"` + A3 A `name:"third"` + } + require.NoError(t, ch.Provide(func() ret { + return ret{A1: A{1}, A2: A{2}, A3: A{3}} + }), "provide for three named instances should succeed") + require.NoError(t, c.Invoke(func(p param) { + assert.Equal(t, 1, p.A1.idx) + assert.Equal(t, 3, p.A3.idx) + }), "invoke should succeed, pulling out two named instances") + }) + + t.Run("named instances can be created with Name option", func(t *testing.T) { + c := New() + + ch := c.Child("child") + + type A struct{ idx int } + + buildConstructor := func(idx int) func() A { + return func() A { return A{idx: idx} } + } + + require.NoError(t, ch.Provide(buildConstructor(1), Name("first"))) + require.NoError(t, ch.Provide(buildConstructor(2), Name("second"))) + require.NoError(t, ch.Provide(buildConstructor(3), Name("third"))) + + type param struct { + In + + A1 A `name:"first"` + A3 A `name:"third"` + } + + require.NoError(t, c.Invoke(func(p param) { + assert.Equal(t, 1, p.A1.idx) + assert.Equal(t, 3, p.A3.idx) + }), "invoke should succeed, pulling out two named instances") + }) + + t.Run("named and unnamed instances coexist", func(t *testing.T) { + c := New() + + ch := c.Child("child") + type A struct{ idx int } + + type out struct { + Out + + A `name:"foo"` + } + + require.NoError(t, ch.Provide(func() out { return out{A: A{1}} })) + require.NoError(t, ch.Provide(func() A { return A{2} })) + + type in struct { + In + + A1 A `name:"foo"` + A2 A + } + require.NoError(t, c.Invoke(func(i in) { + assert.Equal(t, 1, i.A1.idx) + assert.Equal(t, 2, i.A2.idx) + })) + }) + + t.Run("named instances recurse", func(t *testing.T) { + c := New() + + ch := c.Child("child") + type A struct{ idx int } + + type Ret1 struct { + Out + + A1 A `name:"first"` + } + type Ret2 struct { + Ret1 + + A2 A `name:"second"` + } + type param struct { + In + + A1 A `name:"first"` // should come from ret1 through ret2 + A2 A `name:"second"` // should come from ret2 + } + require.NoError(t, ch.Provide(func() Ret2 { + return Ret2{ + Ret1: Ret1{ + A1: A{1}, + }, + A2: A{2}, + } + })) + require.NoError(t, c.Invoke(func(p param) { + assert.Equal(t, 1, p.A1.idx) + assert.Equal(t, 2, p.A2.idx) + }), "invoke should succeed, pulling out two named instances") + }) + + t.Run("named instances do not cause cycles", func(t *testing.T) { + c := New() + + ch := c.Child("child") + type A struct{ idx int } + type param struct { + In + A `name:"uno"` + } + type paramBoth struct { + In + + A1 A `name:"uno"` + A2 A `name:"dos"` + } + type retUno struct { + Out + A `name:"uno"` + } + type retDos struct { + Out + A `name:"dos"` + } + + require.NoError(t, ch.Provide(func() retUno { + return retUno{A: A{1}} + }), `should be able to provide A[name="uno"]`) + require.NoError(t, ch.Provide(func(p param) retDos { + return retDos{A: A{2}} + }), `A[name="dos"] should be able to rely on A[name="uno"]`) + require.NoError(t, c.Invoke(func(p paramBoth) { + assert.Equal(t, 1, p.A1.idx) + assert.Equal(t, 2, p.A2.idx) + }), "both objects should be successfully resolved on Invoke") + }) + + t.Run("struct constructor with as interface option", func(t *testing.T) { + c := New() + + ch := c.Child("child") + + provider := ch.Provide( + func() *bytes.Buffer { + var buf bytes.Buffer + buf.WriteString("foo") + return &buf + }, + As(new(fmt.Stringer), new(io.Reader)), + ) + + require.NoError(t, provider, "provide failed") + + require.NoError(t, c.Invoke( + func(s fmt.Stringer, r io.Reader) { + require.Equal(t, "foo", s.String(), "invoke got new buffer") + got, err := ioutil.ReadAll(r) + assert.NoError(t, err, "failed to read from reader") + require.Equal(t, "foo", string(got), "invoke got new buffer") + }, + ), "invoke failed") + }) + + t.Run("As with Name", func(t *testing.T) { + c := New() + + ch := c.Child("child") + + require.NoError(t, ch.Provide( + func() *bytes.Buffer { + return bytes.NewBufferString("foo") + }, + As(new(io.Reader)), + Name("buff"), + ), "failed to provide") + + type in struct { + In + + Buffer *bytes.Buffer `name:"buff"` + Reader io.Reader `name:"buff"` + } + + require.NoError(t, c.Invoke(func(got in) { + assert.NotNil(t, got.Buffer, "buffer must not be nil") + + assert.True(t, got.Buffer == got.Reader, + "reader and buffer must be the same object") + + body, err := ioutil.ReadAll(got.Reader) + require.NoError(t, err, "failed to read buffer body") + assert.Equal(t, "foo", string(body)) + })) + }) + + t.Run("As same interface", func(t *testing.T) { + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() io.Reader { + panic("this function should not be called") + }, As(new(io.Reader))), "failed to provide") + }) + + t.Run("As different interface", func(t *testing.T) { + c := New() + + ch := c.Child("child") + require.NoError(t, ch.Provide(func() io.ReadCloser { + panic("this function should not be called") + }, As(new(io.Reader), new(io.Closer))), "failed to provide") + }) + + t.Run("invoke on a type that depends on named parameters", func(t *testing.T) { + c := New() + + ch := c.Child("child") + type A struct{ idx int } + type B struct{ sum int } + type param struct { + In + + A1 *A `name:"foo"` + A2 *A `name:"bar"` + A3 *A `name:"baz" optional:"true"` + } + type ret struct { + Out + + A1 *A `name:"foo"` + A2 *A `name:"bar"` + } + require.NoError(t, ch.Provide(func() (ret, error) { + return ret{ + A1: &A{1}, + A2: &A{2}, + }, nil + }), "should be able to provide A1 and A2 into the graph") + require.NoError(t, ch.Provide(func(p param) *B { + return &B{sum: p.A1.idx + p.A2.idx} + }), "should be able to provide *B that relies on two named types") + require.NoError(t, c.Invoke(func(b *B) { + require.Equal(t, 3, b.sum) + })) + }) + + t.Run("optional and named ordering doesn't matter", func(t *testing.T) { + type param1 struct { + In + + Foo *struct{} `name:"foo" optional:"true"` + } + + type param2 struct { + In + + Foo *struct{} `optional:"true" name:"foo"` + } + + t.Run("optional", func(t *testing.T) { + c := New() + + ch := c.Child("child") + + called1 := false + require.NoError(t, ch.Invoke(func(p param1) { + called1 = true + assert.Nil(t, p.Foo) + })) + + called2 := false + require.NoError(t, ch.Invoke(func(p param2) { + called2 = true + assert.Nil(t, p.Foo) + })) + + assert.True(t, called1) + assert.True(t, called2) + }) + + t.Run("named", func(t *testing.T) { + c := New() + + ch := c.Child("child") + + require.NoError(t, ch.Provide(func() *struct{} { + return &struct{}{} + }, Name("foo"))) + + called1 := false + require.NoError(t, c.Invoke(func(p param1) { + called1 = true + assert.NotNil(t, p.Foo) + })) + + called2 := false + require.NoError(t, c.Invoke(func(p param2) { + called2 = true + assert.NotNil(t, p.Foo) + })) + + assert.True(t, called1) + assert.True(t, called2) + }) + + }) + + t.Run("dynamically generated dig.In", func(t *testing.T) { + // This test verifies that a dig.In generated using reflect.StructOf + // works with our dig.In detection logic. + c := New() + + ch := c.Child("child") + + type type1 struct{} + type type2 struct{} + + var gave *type1 + new1 := func() *type1 { + require.Nil(t, gave, "constructor must be called only once") + gave = &type1{} + return gave + } + + require.NoError(t, ch.Provide(new1), "failed to provide constructor") + + // We generate a struct that embeds dig.In. + // + // Note that the fix for https://github.com/golang/go/issues/18780 + // requires that StructField.Name is always set but versions of Go + // older than 1.9 expect Name to be empty for embedded fields. + // + // We use utils_for_go19_test and utils_for_pre_go19_test with build + // tags to implement this behavior differently in the two Go versions. + + inType := reflect.StructOf([]reflect.StructField{ + anonymousField(reflect.TypeOf(In{})), + { + Name: "Foo", + Type: reflect.TypeOf(&type1{}), + }, + { + Name: "Bar", + Type: reflect.TypeOf(&type2{}), + Tag: `optional:"true"`, + }, + }) + + // We generate a function that relies on that struct and validates the + // result. + fn := reflect.MakeFunc( + reflect.FuncOf([]reflect.Type{inType}, nil /* returns */, false /* variadic */), + func(args []reflect.Value) []reflect.Value { + require.Len(t, args, 1, "expected only one argument") + require.Equal(t, reflect.Struct, args[0].Kind(), "argument must be a struct") + require.Equal(t, 3, args[0].NumField(), "struct must have two fields") + + t1, ok := args[0].Field(1).Interface().(*type1) + require.True(t, ok, "field must be a type1") + require.NotNil(t, t1, "value must not be nil") + require.True(t, t1 == gave, "value must match constructor's return value") + + require.True(t, args[0].Field(2).IsNil(), "type2 must be nil") + return nil + }, + ) + + require.NoError(t, c.Invoke(fn.Interface()), "invoke failed") + }) + + t.Run("dynamically generated dig.Out", func(t *testing.T) { + // This test verifies that a dig.Out generated using reflect.StructOf + // works with our dig.Out detection logic. + + c := New() + + ch := c.Child("child") + + type A struct{ Value int } + + outType := reflect.StructOf([]reflect.StructField{ + anonymousField(reflect.TypeOf(Out{})), + { + Name: "Foo", + Type: reflect.TypeOf(&A{}), + Tag: `name:"foo"`, + }, + { + Name: "Bar", + Type: reflect.TypeOf(&A{}), + Tag: `name:"bar"`, + }, + }) + + fn := reflect.MakeFunc( + reflect.FuncOf(nil /* params */, []reflect.Type{outType}, false /* variadic */), + func([]reflect.Value) []reflect.Value { + result := reflect.New(outType).Elem() + result.Field(1).Set(reflect.ValueOf(&A{Value: 1})) + result.Field(2).Set(reflect.ValueOf(&A{Value: 2})) + return []reflect.Value{result} + }, + ) + require.NoError(t, ch.Provide(fn.Interface()), "provide failed") + + type params struct { + In + + Foo *A `name:"foo"` + Bar *A `name:"bar"` + Baz *A `name:"baz" optional:"true"` + } + + require.NoError(t, c.Invoke(func(p params) { + assert.Equal(t, &A{Value: 1}, p.Foo, "Foo must match") + assert.Equal(t, &A{Value: 2}, p.Bar, "Bar must match") + assert.Nil(t, p.Baz, "Baz must be unset") + }), "invoke failed") + }) + + t.Run("variadic arguments invoke", func(t *testing.T) { + c := New() + + ch := c.Child("child") + + type A struct{} + + var gaveA *A + require.NoError(t, ch.Provide(func() *A { + gaveA = &A{} + return gaveA + }), "failed to provide A") + + require.NoError(t, ch.Provide(func() []*A { + panic("[]*A constructor must not be called.") + }), "failed to provide A slice") + + require.NoError(t, c.Invoke(func(a *A, as ...*A) { + require.NotNil(t, a, "A must not be nil") + require.True(t, a == gaveA, "A must match") + require.Empty(t, as, "varargs must be empty") + }), "failed to invoke") + }) + + t.Run("variadic arguments dependency", func(t *testing.T) { + c := New() + + ch := c.Child("child") + + type A struct{} + type B struct{} + + var gaveA *A + require.NoError(t, ch.Provide(func() *A { + gaveA = &A{} + return gaveA + }), "failed to provide A") + + require.NoError(t, ch.Provide(func() []*A { + panic("[]*A constructor must not be called.") + }), "failed to provide A slice") + + var gaveB *B + require.NoError(t, ch.Provide(func(a *A, as ...*A) *B { + require.NotNil(t, a, "A must not be nil") + require.True(t, a == gaveA, "A must match") + require.Empty(t, as, "varargs must be empty") + gaveB = &B{} + return gaveB + }), "failed to provide B") + + require.NoError(t, c.Invoke(func(b *B) { + require.NotNil(t, b, "B must not be nil") + require.True(t, b == gaveB, "B must match") + }), "failed to invoke") + }) + + t.Run("non-error return arguments from invoke are ignored", func(t *testing.T) { + c := New() + + ch := c.Child("child") + type A struct{} + type B struct{} + + require.NoError(t, ch.Provide(func() A { return A{} })) + require.NoError(t, c.Invoke(func(A) B { return B{} })) + + err := c.Invoke(func(B) {}) + require.Error(t, err, "invoking with B param should error out") + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".TestChildren.func\S+ \(\S+/src/go.uber.org/dig/dig_test.go:\d+\):`, + "type dig.B is not in the container,", + "did you mean to Provide it?", + ) + }) + + t.Run("parent providers available to deeply nested children", func(t *testing.T) { + c := New() + + var b *bytes.Buffer + require.NoError(t, c.Provide(func() *bytes.Buffer { + b = &bytes.Buffer{} + return b + }), "provide failed") + ch := c.Child("1").Child("2").Child("3") + require.NoError(t, ch.Invoke(func(got *bytes.Buffer) { + require.NotNil(t, got, "invoke got nil buffer") + require.True(t, got == b, "invoke got wrong buffer") + }), "invoke failed") + }) + + t.Run("multiple sub graphs", func(t *testing.T) { + c := New() + + cc := make([]*Container, 0, 5) + for i := 0; i < 5; i++ { + cc = append(cc, c.Child(strconv.Itoa(i))) + } + + for i := 0; i < 5; i++ { + cc = append(cc, cc[1].Child(strconv.Itoa(i))) + } + + var b *bytes.Buffer + require.NoError(t, cc[2].Provide(func() *bytes.Buffer { + b = &bytes.Buffer{} + return b + }), "provide failed") + require.NoError(t, c.Invoke(func(got *bytes.Buffer) { + require.NotNil(t, got, "invoke got nil buffer") + require.True(t, got == b, "invoke got wrong buffer") + }), "invoke failed") + }) +} + func TestGroups(t *testing.T) { t.Run("empty slice received without provides", func(t *testing.T) { c := New() From 59348c5bcb4c6ee6551bb2a25be9ee4e18638c97 Mon Sep 17 00:00:00 2001 From: Sri Krishna Paritala Date: Tue, 30 Apr 2019 17:21:57 +0530 Subject: [PATCH 04/25] Added Tests for Group operations --- dig_test.go | 356 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) diff --git a/dig_test.go b/dig_test.go index a323f345..8688caaf 100644 --- a/dig_test.go +++ b/dig_test.go @@ -2311,6 +2311,362 @@ func TestGroups(t *testing.T) { }) } +func TestChildrenGroups(t *testing.T) { + t.Run("empty slice received without provides", func(t *testing.T) { + c := New() + + ch := c.Child("child") + type in struct { + In + + Values []int `group:"foo"` + } + + require.NoError(t, ch.Invoke(func(i in) { + require.Empty(t, i.Values) + }), "failed to invoke") + }) + + t.Run("values are provided", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + ch := c.Child("child") + + type out struct { + Out + + Value int `group:"val"` + } + + provide := func(i int) { + require.NoError(t, ch.Provide(func() out { + return out{Value: i} + }), "failed to provide ") + } + + provide(1) + provide(2) + provide(3) + + type in struct { + In + + Values []int `group:"val"` + } + + require.NoError(t, c.Invoke(func(i in) { + assert.Equal(t, []int{2, 3, 1}, i.Values) + }), "invoke failed") + }) + + t.Run("groups are provided via option", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + ch := c.Child("child") + + provide := func(i int) { + require.NoError(t, ch.Provide(func() int { + return i + }, Group("val")), "failed to provide ") + } + + provide(1) + provide(2) + provide(3) + + type in struct { + In + + Values []int `group:"val"` + } + + require.NoError(t, c.Invoke(func(i in) { + assert.Equal(t, []int{2, 3, 1}, i.Values) + }), "invoke failed") + }) + + t.Run("different types may be grouped", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + ch := c.Child("child") + + provide := func(i int, s string) { + require.NoError(t, ch.Provide(func() (int, string) { + return i, s + }, Group("val")), "failed to provide ") + } + + provide(1, "a") + provide(2, "b") + provide(3, "c") + + type in struct { + In + + Ivalues []int `group:"val"` + Svalues []string `group:"val"` + } + + require.NoError(t, c.Invoke(func(i in) { + assert.Equal(t, []int{2, 3, 1}, i.Ivalues) + assert.Equal(t, []string{"a", "c", "b"}, i.Svalues) + }), "invoke failed") + }) + + t.Run("group options may not be provided for result structs", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + ch := c.Child("child") + + type out struct { + Out + + Value int `group:"val"` + } + + func(i int) { + require.Error(t, ch.Provide(func() { + t.Fatal("This should not be called") + }, Group("val")), "This Provide should fail") + }(1) + }) + + t.Run("constructor is called at most once", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + ch := c.Child("child") + + type out struct { + Out + + Result string `group:"s"` + } + + calls := make(map[string]int) + + provide := func(i string) { + require.NoError(t, ch.Provide(func() out { + calls[i]++ + return out{Result: i} + }), "failed to provide") + } + + provide("foo") + provide("bar") + provide("baz") + + type in struct { + In + + Results []string `group:"s"` + } + + // Expected value of in.Results in consecutive calls. + expected := [][]string{ + {"bar", "baz", "foo"}, + {"foo", "baz", "bar"}, + {"baz", "bar", "foo"}, + {"bar", "foo", "baz"}, + } + + for i, want := range expected { + require.NoError(t, c.Invoke(func(i in) { + require.Equal(t, want, i.Results) + }), "invoke %d failed", i) + } + + for s, v := range calls { + assert.Equal(t, 1, v, "constructor for %q called too many times", s) + } + }) + + t.Run("consume groups in constructor", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + ch := c.Child("child") + + type out struct { + Out + + Result []string `group:"hi"` + } + + provideStrings := func(strings ...string) { + require.NoError(t, ch.Provide(func() out { + return out{Result: strings} + }), "failed to provide") + } + + provideStrings("1", "2") + provideStrings("3", "4", "5") + provideStrings("6") + provideStrings("7", "8", "9", "10") + + type setParams struct { + In + + Strings [][]string `group:"hi"` + } + require.NoError(t, ch.Provide(func(p setParams) map[string]struct{} { + m := make(map[string]struct{}) + for _, ss := range p.Strings { + for _, s := range ss { + m[s] = struct{}{} + } + } + return m + }), "failed to provide set constructor") + + require.NoError(t, c.Invoke(func(got map[string]struct{}) { + assert.Equal(t, map[string]struct{}{ + "1": {}, "2": {}, "3": {}, "4": {}, "5": {}, + "6": {}, "7": {}, "8": {}, "9": {}, "10": {}, + }, got) + }), "failed to invoke") + }) + + t.Run("provide multiple values", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + ch := c.Child("child") + + type outInt struct { + Out + Int int `group:"foo"` + } + + provideInt := func(i int) { + require.NoError(t, ch.Provide(func() (outInt, error) { + return outInt{Int: i}, nil + }), "failed to provide int") + } + + type outString struct { + Out + String string `group:"foo"` + } + + provideString := func(s string) { + require.NoError(t, ch.Provide(func() outString { + return outString{String: s} + }), "failed to provide string") + } + + type outBoth struct { + Out + + Int int `group:"foo"` + String string `group:"foo"` + } + + provideBoth := func(i int, s string) { + require.NoError(t, ch.Provide(func() (outBoth, error) { + return outBoth{Int: i, String: s}, nil + }), "failed to provide both") + } + + provideInt(1) + provideString("foo") + provideBoth(2, "bar") + provideString("baz") + provideInt(3) + provideBoth(4, "qux") + provideBoth(5, "quux") + provideInt(6) + provideInt(7) + + type in struct { + In + + Ints []int `group:"foo"` + Strings []string `group:"foo"` + } + + require.NoError(t, c.Invoke(func(got in) { + assert.Equal(t, in{ + Ints: []int{5, 3, 4, 1, 6, 7, 2}, + Strings: []string{"foo", "bar", "baz", "quux", "qux"}, + }, got) + }), "failed to invoke") + }) + + t.Run("duplicate values are supported", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + ch := c.Child("child") + + type out struct { + Out + + Result string `group:"s"` + } + + provide := func(i string) { + require.NoError(t, ch.Provide(func() out { + return out{Result: i} + }), "failed to provide") + } + + provide("a") + provide("b") + provide("c") + provide("a") + provide("d") + provide("d") + provide("a") + provide("e") + + type stringSlice []string + + type in struct { + In + + Strings stringSlice `group:"s"` + } + + require.NoError(t, c.Invoke(func(i in) { + assert.Equal(t, + stringSlice{"d", "c", "a", "a", "d", "e", "b", "a"}, + i.Strings) + }), "failed to invoke") + }) + + t.Run("failure to build a grouped value fails everything", func(t *testing.T) { + c := New(setRand(rand.New(rand.NewSource(0)))) + ch := c.Child("child") + + type out struct { + Out + + Result string `group:"x"` + } + + require.NoError(t, ch.Provide(func() (out, error) { + return out{Result: "foo"}, nil + }), "failed to provide") + + var gaveErr error + require.NoError(t, ch.Provide(func() (out, error) { + gaveErr = errors.New("great sadness") + return out{}, gaveErr + }), "failed to provide") + + require.NoError(t, ch.Provide(func() out { + return out{Result: "bar"} + }), "failed to provide") + + type in struct { + In + + Strings []string `group:"x"` + } + + err := c.Invoke(func(i in) { + require.FailNow(t, "this function must not be called") + }) + require.Error(t, err, "expected failure") + assertErrorMatches(t, err, + `could not build arguments for function "go.uber.org/dig".TestChildrenGroups`, + `could not build value group string\[group="x"\]:`, + `function "go.uber.org/dig".TestChildrenGroups\S+ \(\S+:\d+\) returned a non-nil error:`, + "great sadness", + ) + assert.Equal(t, gaveErr, RootCause(err)) + }) +} + // --- END OF END TO END TESTS func TestProvideConstructorErrors(t *testing.T) { From 4546ac5a0aaf6eb80589b2328ee90ebba2a05747 Mon Sep 17 00:00:00 2001 From: Sri Krishna Paritala Date: Tue, 30 Apr 2019 17:25:08 +0530 Subject: [PATCH 05/25] Removed: panic if name is empty --- dig.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dig.go b/dig.go index 064efa1d..31f0ff6d 100644 --- a/dig.go +++ b/dig.go @@ -526,10 +526,6 @@ func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error { // The name of the child is for observability purposes only. As such, it // does not have to be unique across different children of the container. func (c *Container) Child(name string) *Container { - if name == "" { - panic("dig: name should not be empty") - } - child := &Container{ providers: make(map[key][]*node), values: make(map[key]reflect.Value), From 364da8c79af4d13ee68851dfe39b13917cc3f23e Mon Sep 17 00:00:00 2001 From: Sri Krishna Paritala Date: Tue, 30 Apr 2019 17:56:08 +0530 Subject: [PATCH 06/25] Added tests for provide failures --- dig.go | 12 ++++++++++++ dig_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/dig.go b/dig.go index 31f0ff6d..9431dfee 100644 --- a/dig.go +++ b/dig.go @@ -714,6 +714,18 @@ func (cv connectionVisitor) checkKey(k key, path string) error { "cannot provide %v from %v: already provided by %v", k, path, strings.Join(cons, "; ")) } + + if ps := cv.c.getRoot().getValueProviders(k.name, k.t); len(ps) > 0 { + cons := make([]string, len(ps)) + for i, p := range ps { + cons[i] = fmt.Sprint(p.Location()) + } + + return fmt.Errorf( + "cannot provide %v from %v: already provided by %v", + k, path, strings.Join(cons, "; ")) + } + return nil } diff --git a/dig_test.go b/dig_test.go index 8688caaf..02ac296c 100644 --- a/dig_test.go +++ b/dig_test.go @@ -3488,6 +3488,34 @@ func TestProvideFailures(t *testing.T) { assert.Contains(t, err.Error(), "cannot provide *bytes.Buffer") assert.Contains(t, err.Error(), "already provided") }) + + t.Run("provide multiple instances with the same name in different children", func(t *testing.T) { + c := New() + + ca := c.Child("1") + cb := ca.Child("2") + type A struct{} + type ret1 struct { + Out + *A `name:"foo"` + } + type ret2 struct { + Out + *A `name:"foo"` + } + require.NoError(t, ca.Provide(func() ret1 { + return ret1{A: &A{}} + })) + err := cb.Provide(func() ret2 { + return ret2{A: &A{}} + }) + require.Error(t, err, "expected error on the second provide") + assertErrorMatches(t, err, + `function "go.uber.org/dig".TestProvideFailures\S+ \(\S+:\d+\) cannot be provided:`, + `cannot provide \*dig.A\[name="foo"\] from \[0\].A:`, + `already provided by "go.uber.org/dig".TestProvideFailures\S+`, + ) + }) } func TestInvokeFailures(t *testing.T) { From 66b0bb10a98100f74691561a354a1c049d08fb3b Mon Sep 17 00:00:00 2001 From: Sri Krishna Paritala Date: Tue, 30 Apr 2019 17:58:57 +0530 Subject: [PATCH 07/25] Remove redundant check in provide --- dig.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/dig.go b/dig.go index 9431dfee..f44bddc9 100644 --- a/dig.go +++ b/dig.go @@ -704,17 +704,6 @@ func (cv connectionVisitor) checkKey(k key, path string) error { "cannot provide %v from %v: already provided by %v", k, path, conflict) } - if ps := cv.c.providers[k]; len(ps) > 0 { - cons := make([]string, len(ps)) - for i, p := range ps { - cons[i] = fmt.Sprint(p.Location()) - } - - return fmt.Errorf( - "cannot provide %v from %v: already provided by %v", - k, path, strings.Join(cons, "; ")) - } - if ps := cv.c.getRoot().getValueProviders(k.name, k.t); len(ps) > 0 { cons := make([]string, len(ps)) for i, p := range ps { From f91df49003730dabe94c9cac9cbe90136c53966f Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Wed, 1 May 2019 08:08:20 +0530 Subject: [PATCH 08/25] Apply suggestions from code review Co-Authored-By: srikrsna --- dig.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/dig.go b/dig.go index f44bddc9..4250e713 100644 --- a/dig.go +++ b/dig.go @@ -230,13 +230,13 @@ type Container struct { // Defer acyclic check on provide until Invoke. deferAcyclicVerification bool - // Name of the container + // Name of the container. name string - // Sub graphs of the container + // Sub graphs of the container. children []*Container - // Parent is the container that spawned this + // Parent is the container that spawned this. parent *Container } @@ -340,6 +340,8 @@ func setRand(r *rand.Rand) Option { }) } +// knownTypes returns the types known to this container, including types known +// by its descendants. func (c *Container) knownTypes() []reflect.Type { getKnowTypes := func(c *Container) []reflect.Type { typeSet := make(map[reflect.Type]struct{}, len(c.providers)) @@ -530,7 +532,7 @@ func (c *Container) Child(name string) *Container { providers: make(map[key][]*node), values: make(map[key]reflect.Value), groups: make(map[key][]reflect.Value), - rand: rand.New(rand.NewSource(time.Now().UnixNano())), + rand: c.rand, name: name, parent: c, } From e83edb4e0b93615718baf909aeec3747298665a2 Mon Sep 17 00:00:00 2001 From: Sri Krishna Paritala Date: Fri, 3 May 2019 13:08:24 +0530 Subject: [PATCH 09/25] Removed duplicate tests. Added tests for cases where sub graphs can fail --- dig.go | 29 +- dig_test.go | 2020 ++++++++++----------------------------------------- 2 files changed, 379 insertions(+), 1670 deletions(-) diff --git a/dig.go b/dig.go index 4250e713..96b90a00 100644 --- a/dig.go +++ b/dig.go @@ -343,23 +343,18 @@ func setRand(r *rand.Rand) Option { // knownTypes returns the types known to this container, including types known // by its descendants. func (c *Container) knownTypes() []reflect.Type { - getKnowTypes := func(c *Container) []reflect.Type { - typeSet := make(map[reflect.Type]struct{}, len(c.providers)) - for k := range c.providers { - typeSet[k.t] = struct{}{} - } - - types := make([]reflect.Type, 0, len(typeSet)) - for t := range typeSet { - types = append(types, t) - } + typeSet := make(map[reflect.Type]struct{}, len(c.providers)) + for k := range c.providers { + typeSet[k.t] = struct{}{} + } - return types + types := make([]reflect.Type, 0, len(typeSet)) + for t := range typeSet { + types = append(types, t) } - types := make([]reflect.Type, 0) - for _, c := range append(c.children, c) { - types = append(types, getKnowTypes(c)...) + for _, c := range append(c.children) { + types = append(types, c.knownTypes()...) } sort.Sort(byTypeName(types)) @@ -585,7 +580,7 @@ func (c *Container) provide(ctor interface{}, opts provideOptions) error { if c.deferAcyclicVerification { continue } - if err := verifyAcyclic(c, n, k); err != nil { + if err := verifyAcyclic(c.getRoot(), n, k); err != nil { c.providers[k] = oldProviders return err } @@ -602,7 +597,7 @@ func (c *Container) findAndValidateResults(n *node) (map[key]struct{}, error) { var err error keyPaths := make(map[key]string) walkResult(n.ResultList(), connectionVisitor{ - c: c, + c: c.getRoot(), n: n, err: &err, keyPaths: keyPaths, @@ -706,7 +701,7 @@ func (cv connectionVisitor) checkKey(k key, path string) error { "cannot provide %v from %v: already provided by %v", k, path, conflict) } - if ps := cv.c.getRoot().getValueProviders(k.name, k.t); len(ps) > 0 { + if ps := cv.c.getValueProviders(k.name, k.t); len(ps) > 0 { cons := make([]string, len(ps)) for i, p := range ps { cons[i] = fmt.Sprint(p.Location()) diff --git a/dig_test.go b/dig_test.go index 02ac296c..31271860 100644 --- a/dig_test.go +++ b/dig_test.go @@ -37,17 +37,23 @@ import ( "github.com/stretchr/testify/require" ) +type newFunc func(...Option) (func(interface{}, ...ProvideOption) error, func(interface{}, ...InvokeOption) error) + func TestEndToEndSuccess(t *testing.T) { + testSubGraphs(t, testEndToEndSuccess) +} + +func testEndToEndSuccess(t *testing.T, fp newFunc) { t.Parallel() t.Run("pointer constructor", func(t *testing.T) { - c := New() + provide, invoke := fp() var b *bytes.Buffer - require.NoError(t, c.Provide(func() *bytes.Buffer { + require.NoError(t, provide(func() *bytes.Buffer { b = &bytes.Buffer{} return b }), "provide failed") - require.NoError(t, c.Invoke(func(got *bytes.Buffer) { + require.NoError(t, invoke(func(got *bytes.Buffer) { require.NotNil(t, got, "invoke got nil buffer") require.True(t, got == b, "invoke got wrong buffer") }), "invoke failed") @@ -57,34 +63,34 @@ func TestEndToEndSuccess(t *testing.T) { // Dig shouldn't forbid this - it's perfectly reasonable to explicitly // provide a typed nil, since that's often a convenient way to supply a // default no-op implementation. - c := New() - require.NoError(t, c.Provide(func() *bytes.Buffer { return nil }), "provide failed") - require.NoError(t, c.Invoke(func(b *bytes.Buffer) { + provide, invoke := fp() + require.NoError(t, provide(func() *bytes.Buffer { return nil }), "provide failed") + require.NoError(t, invoke(func(b *bytes.Buffer) { require.Nil(t, b, "expected to get nil buffer") }), "invoke failed") }) t.Run("struct constructor", func(t *testing.T) { - c := New() - require.NoError(t, c.Provide(func() bytes.Buffer { + provide, invoke := fp() + require.NoError(t, provide(func() bytes.Buffer { var buf bytes.Buffer buf.WriteString("foo") return buf }), "provide failed") - require.NoError(t, c.Invoke(func(b bytes.Buffer) { + require.NoError(t, invoke(func(b bytes.Buffer) { // ensure we're getting back the buffer we put in require.Equal(t, "foo", b.String(), "invoke got new buffer") }), "invoke failed") }) t.Run("slice constructor", func(t *testing.T) { - c := New() + provide, invoke := fp() b1 := &bytes.Buffer{} b2 := &bytes.Buffer{} - require.NoError(t, c.Provide(func() []*bytes.Buffer { + require.NoError(t, provide(func() []*bytes.Buffer { return []*bytes.Buffer{b1, b2} }), "provide failed") - require.NoError(t, c.Invoke(func(bs []*bytes.Buffer) { + require.NoError(t, invoke(func(bs []*bytes.Buffer) { require.Equal(t, 2, len(bs), "invoke got unexpected number of buffers") require.True(t, b1 == bs[0], "first item did not match") require.True(t, b2 == bs[1], "second item did not match") @@ -92,56 +98,56 @@ func TestEndToEndSuccess(t *testing.T) { }) t.Run("array constructor", func(t *testing.T) { - c := New() + provide, invoke := fp() bufs := [1]*bytes.Buffer{{}} - require.NoError(t, c.Provide(func() [1]*bytes.Buffer { return bufs }), "provide failed") - require.NoError(t, c.Invoke(func(bs [1]*bytes.Buffer) { + require.NoError(t, provide(func() [1]*bytes.Buffer { return bufs }), "provide failed") + require.NoError(t, invoke(func(bs [1]*bytes.Buffer) { require.NotNil(t, bs[0], "invoke got new array") }), "invoke failed") }) t.Run("map constructor", func(t *testing.T) { - c := New() - require.NoError(t, c.Provide(func() map[string]string { + provide, invoke := fp() + require.NoError(t, provide(func() map[string]string { return map[string]string{} }), "provide failed") - require.NoError(t, c.Invoke(func(m map[string]string) { + require.NoError(t, invoke(func(m map[string]string) { require.NotNil(t, m, "invoke got zero value map") }), "invoke failed") }) t.Run("channel constructor", func(t *testing.T) { - c := New() - require.NoError(t, c.Provide(func() chan int { + provide, invoke := fp() + require.NoError(t, provide(func() chan int { return make(chan int) }), "provide failed") - require.NoError(t, c.Invoke(func(ch chan int) { + require.NoError(t, invoke(func(ch chan int) { require.NotNil(t, ch, "invoke got nil chan") }), "invoke failed") }) t.Run("func constructor", func(t *testing.T) { - c := New() - require.NoError(t, c.Provide(func() func(int) { + provide, invoke := fp() + require.NoError(t, provide(func() func(int) { return func(int) {} }), "provide failed") - require.NoError(t, c.Invoke(func(f func(int)) { + require.NoError(t, invoke(func(f func(int)) { require.NotNil(t, f, "invoke got nil function pointer") }), "invoke failed") }) t.Run("interface constructor", func(t *testing.T) { - c := New() - require.NoError(t, c.Provide(func() io.Writer { + provide, invoke := fp() + require.NoError(t, provide(func() io.Writer { return &bytes.Buffer{} }), "provide failed") - require.NoError(t, c.Invoke(func(w io.Writer) { + require.NoError(t, invoke(func(w io.Writer) { require.NotNil(t, w, "invoke got nil interface") }), "invoke failed") }) t.Run("param", func(t *testing.T) { - c := New() + provide, invoke := fp() type contents string type Args struct { In @@ -150,16 +156,16 @@ func TestEndToEndSuccess(t *testing.T) { } require.NoError(t, - c.Provide(func(args Args) *bytes.Buffer { + provide(func(args Args) *bytes.Buffer { require.NotEmpty(t, args.Contents, "contents must not be empty") return bytes.NewBufferString(string(args.Contents)) }), "provide constructor failed") require.NoError(t, - c.Provide(func() contents { return "hello world" }), + provide(func() contents { return "hello world" }), "provide value failed") - require.NoError(t, c.Invoke(func(buff *bytes.Buffer) { + require.NoError(t, invoke(func(buff *bytes.Buffer) { out, err := ioutil.ReadAll(buff) require.NoError(t, err, "read from buffer failed") require.Equal(t, "hello world", string(out), "contents don't match") @@ -167,8 +173,8 @@ func TestEndToEndSuccess(t *testing.T) { }) t.Run("invoke param", func(t *testing.T) { - c := New() - require.NoError(t, c.Provide(func() *bytes.Buffer { + provide, invoke := fp() + require.NoError(t, provide(func() *bytes.Buffer { return new(bytes.Buffer) }), "provide failed") @@ -178,7 +184,7 @@ func TestEndToEndSuccess(t *testing.T) { *bytes.Buffer } - require.NoError(t, c.Invoke(func(args Args) { + require.NoError(t, invoke(func(args Args) { require.NotNil(t, args.Buffer, "invoke got nil buffer") })) }) @@ -189,8 +195,8 @@ func TestEndToEndSuccess(t *testing.T) { called bool ) - c := New() - require.NoError(t, c.Provide(func() *bytes.Buffer { + provide, invoke := fp() + require.NoError(t, provide(func() *bytes.Buffer { require.False(t, called, "constructor must be called exactly once") called = true buff = new(bytes.Buffer) @@ -205,7 +211,7 @@ func TestEndToEndSuccess(t *testing.T) { Buffer *bytes.Buffer } - require.NoError(t, c.Invoke(func(args Args) { + require.NoError(t, invoke(func(args Args) { require.True(t, called, "constructor must be called first") require.NotNil(t, args.Buffer, "invoke got nil buffer") require.True(t, args.Buffer == buff, "buffer must match constructor's return value") @@ -231,15 +237,15 @@ func TestEndToEndSuccess(t *testing.T) { called bool ) - c := New() - require.NoError(t, c.Provide(func() *bytes.Buffer { + provide, invoke := fp() + require.NoError(t, provide(func() *bytes.Buffer { require.False(t, called, "constructor must be called exactly once") called = true buff = new(bytes.Buffer) return buff }), "provide must not fail") - require.NoError(t, c.Invoke(func(p someParam) { + require.NoError(t, invoke(func(p someParam) { require.True(t, called, "constructor must be called first") require.NotNil(t, p.Buffer, "someParam.Buffer must not be nil") @@ -251,7 +257,7 @@ func TestEndToEndSuccess(t *testing.T) { }) t.Run("multiple-type constructor", func(t *testing.T) { - c := New() + provide, invoke := fp() constructor := func() (*bytes.Buffer, []int, error) { return &bytes.Buffer{}, []int{42}, nil } @@ -259,12 +265,12 @@ func TestEndToEndSuccess(t *testing.T) { assert.NotNil(t, b, "invoke got nil buffer") assert.Equal(t, 1, len(nums), "invoke got empty slice") } - require.NoError(t, c.Provide(constructor), "provide failed") - require.NoError(t, c.Invoke(consumer), "invoke failed") + require.NoError(t, provide(constructor), "provide failed") + require.NoError(t, invoke(consumer), "invoke failed") }) t.Run("multiple-type constructor is called once", func(t *testing.T) { - c := New() + provide, invoke := fp() type A struct{} type B struct{} count := 0 @@ -278,15 +284,15 @@ func TestEndToEndSuccess(t *testing.T) { getB := func(b *B) { assert.NotNil(t, b, "got nil B") } - require.NoError(t, c.Provide(constructor), "provide failed") - require.NoError(t, c.Invoke(getA), "A invoke failed") - require.NoError(t, c.Invoke(getB), "B invoke failed") - require.NoError(t, c.Invoke(func(a *A, b *B) {}), "AB invoke failed") + require.NoError(t, provide(constructor), "provide failed") + require.NoError(t, invoke(getA), "A invoke failed") + require.NoError(t, invoke(getB), "B invoke failed") + require.NoError(t, invoke(func(a *A, b *B) {}), "AB invoke failed") require.Equal(t, 1, count, "Constructor must be called once") }) t.Run("method invocation inside Invoke", func(t *testing.T) { - c := New() + provide, invoke := fp() type A struct{} type B struct{} cA := func() (*A, error) { @@ -296,23 +302,23 @@ func TestEndToEndSuccess(t *testing.T) { return &B{}, nil } getA := func(a *A) { - c.Invoke(func(b *B) { + invoke(func(b *B) { assert.NotNil(t, b, "got nil B") }) assert.NotNil(t, a, "got nil A") } - require.NoError(t, c.Provide(cA), "provide failed") - require.NoError(t, c.Provide(cB), "provide failed") - require.NoError(t, c.Invoke(getA), "A invoke failed") + require.NoError(t, provide(cA), "provide failed") + require.NoError(t, provide(cB), "provide failed") + require.NoError(t, invoke(getA), "A invoke failed") }) t.Run("collections and instances of same type", func(t *testing.T) { - c := New() - require.NoError(t, c.Provide(func() []*bytes.Buffer { + provide, _ := fp() + require.NoError(t, provide(func() []*bytes.Buffer { return []*bytes.Buffer{{}} }), "providing collection failed") - require.NoError(t, c.Provide(func() *bytes.Buffer { + require.NoError(t, provide(func() *bytes.Buffer { return &bytes.Buffer{} }), "providing pointer failed") }) @@ -327,7 +333,7 @@ func TestEndToEndSuccess(t *testing.T) { return &type1{}, &type3{}, &type4{} } - c := New() + provide, invoke := fp() type param struct { In @@ -337,8 +343,8 @@ func TestEndToEndSuccess(t *testing.T) { T4 *type4 `optional:"true"` // optional type present in the graph T5 *type5 `optional:"t"` // optional type NOT in the graph with "yes" } - require.NoError(t, c.Provide(constructor)) - require.NoError(t, c.Invoke(func(p param) { + require.NoError(t, provide(constructor)) + require.NoError(t, invoke(func(p param) { require.NotNil(t, p.T1, "whole param struct should not be nil") assert.Nil(t, p.T2, "optional type not in the graph should return nil") assert.NotNil(t, p.T3, "required type with unrelated tag not in the graph") @@ -358,11 +364,11 @@ func TestEndToEndSuccess(t *testing.T) { myA := A{"string A"} myB := &B{"string B"} - c := New() - require.NoError(t, c.Provide(func() Ret { + provide, invoke := fp() + require.NoError(t, provide(func() Ret { return Ret{A: myA, B: myB} }), "provide for the Ret struct should succeed") - require.NoError(t, c.Invoke(func(a A, b *B) { + require.NoError(t, invoke(func(a A, b *B) { assert.Equal(t, a.name, "string A", "value type should work for dig.Out") assert.Equal(t, b.name, "string B", "pointer should work for dig.Out") assert.True(t, myA == a, "should get the same pointer for &A") @@ -380,31 +386,31 @@ func TestEndToEndSuccess(t *testing.T) { T1 *type1 `optional:"true"` } - c := New() + provide, invoke := fp() var gave *type2 - require.NoError(t, c.Provide(func(p param) *type2 { + require.NoError(t, provide(func(p param) *type2 { require.Nil(t, p.T1, "T1 must be nil") gave = &type2{} return gave }), "provide failed") - require.NoError(t, c.Invoke(func(got *type2) { + require.NoError(t, invoke(func(got *type2) { require.True(t, got == gave, "type2 reference must be the same") }), "invoke failed") }) t.Run("nested dependencies", func(t *testing.T) { - c := New() + provide, invoke := fp() type A struct{ name string } type B struct{ name string } type C struct{ name string } - require.NoError(t, c.Provide(func() A { return A{"->A"} })) - require.NoError(t, c.Provide(func(A) B { return B{"A->B"} })) - require.NoError(t, c.Provide(func(A, B) C { return C{"AB->C"} })) - require.NoError(t, c.Invoke(func(a A, b B, c C) { + require.NoError(t, provide(func() A { return A{"->A"} })) + require.NoError(t, provide(func(A) B { return B{"A->B"} })) + require.NoError(t, provide(func(A, B) C { return C{"AB->C"} })) + require.NoError(t, invoke(func(a A, b B, c C) { assert.Equal(t, a, A{"->A"}) assert.Equal(t, b, B{"A->B"}) assert.Equal(t, c, C{"AB->C"}) @@ -412,14 +418,14 @@ func TestEndToEndSuccess(t *testing.T) { }) t.Run("primitives", func(t *testing.T) { - c := New() - require.NoError(t, c.Provide(func() string { return "piper" }), "string provide failed") - require.NoError(t, c.Provide(func() int { return 42 }), "int provide failed") - require.NoError(t, c.Provide(func() int64 { return 24 }), "int provide failed") - require.NoError(t, c.Provide(func() time.Duration { + provide, invoke := fp() + require.NoError(t, provide(func() string { return "piper" }), "string provide failed") + require.NoError(t, provide(func() int { return 42 }), "int provide failed") + require.NoError(t, provide(func() int64 { return 24 }), "int provide failed") + require.NoError(t, provide(func() time.Duration { return 10 * time.Second }), "time.Duration provide failed") - require.NoError(t, c.Invoke(func(i64 int64, i int, s string, d time.Duration) { + require.NoError(t, invoke(func(i64 int64, i int, s string, d time.Duration) { assert.Equal(t, 42, i) assert.Equal(t, int64(24), i64) assert.Equal(t, "piper", s) @@ -442,9 +448,9 @@ func TestEndToEndSuccess(t *testing.T) { *B C } - c := New() + provide, invoke := fp() - require.NoError(t, c.Provide(func() Ret2 { + require.NoError(t, provide(func() Ret2 { return Ret2{ Ret1: Ret1{ A: &A{}, @@ -453,13 +459,13 @@ func TestEndToEndSuccess(t *testing.T) { C: C{}, } }), "provide for the Ret struct should succeed") - require.NoError(t, c.Invoke(func(a *A, b *B, c C) { + require.NoError(t, invoke(func(a *A, b *B, c C) { require.NotNil(t, a, "*A should be part of the container through Ret2->Ret1") })) }) t.Run("named instances can be created with tags", func(t *testing.T) { - c := New() + provide, invoke := fp() type A struct{ idx int } // returns three named instances of A @@ -478,17 +484,17 @@ func TestEndToEndSuccess(t *testing.T) { A1 A `name:"first"` A3 A `name:"third"` } - require.NoError(t, c.Provide(func() ret { + require.NoError(t, provide(func() ret { return ret{A1: A{1}, A2: A{2}, A3: A{3}} }), "provide for three named instances should succeed") - require.NoError(t, c.Invoke(func(p param) { + require.NoError(t, invoke(func(p param) { assert.Equal(t, 1, p.A1.idx) assert.Equal(t, 3, p.A3.idx) }), "invoke should succeed, pulling out two named instances") }) t.Run("named instances can be created with Name option", func(t *testing.T) { - c := New() + provide, invoke := fp() type A struct{ idx int } @@ -496,9 +502,9 @@ func TestEndToEndSuccess(t *testing.T) { return func() A { return A{idx: idx} } } - require.NoError(t, c.Provide(buildConstructor(1), Name("first"))) - require.NoError(t, c.Provide(buildConstructor(2), Name("second"))) - require.NoError(t, c.Provide(buildConstructor(3), Name("third"))) + require.NoError(t, provide(buildConstructor(1), Name("first"))) + require.NoError(t, provide(buildConstructor(2), Name("second"))) + require.NoError(t, provide(buildConstructor(3), Name("third"))) type param struct { In @@ -507,14 +513,14 @@ func TestEndToEndSuccess(t *testing.T) { A3 A `name:"third"` } - require.NoError(t, c.Invoke(func(p param) { + require.NoError(t, invoke(func(p param) { assert.Equal(t, 1, p.A1.idx) assert.Equal(t, 3, p.A3.idx) }), "invoke should succeed, pulling out two named instances") }) t.Run("named and unnamed instances coexist", func(t *testing.T) { - c := New() + provide, invoke := fp() type A struct{ idx int } type out struct { @@ -523,8 +529,8 @@ func TestEndToEndSuccess(t *testing.T) { A `name:"foo"` } - require.NoError(t, c.Provide(func() out { return out{A: A{1}} })) - require.NoError(t, c.Provide(func() A { return A{2} })) + require.NoError(t, provide(func() out { return out{A: A{1}} })) + require.NoError(t, provide(func() A { return A{2} })) type in struct { In @@ -532,14 +538,14 @@ func TestEndToEndSuccess(t *testing.T) { A1 A `name:"foo"` A2 A } - require.NoError(t, c.Invoke(func(i in) { + require.NoError(t, invoke(func(i in) { assert.Equal(t, 1, i.A1.idx) assert.Equal(t, 2, i.A2.idx) })) }) t.Run("named instances recurse", func(t *testing.T) { - c := New() + provide, invoke := fp() type A struct{ idx int } type Ret1 struct { @@ -558,7 +564,7 @@ func TestEndToEndSuccess(t *testing.T) { A1 A `name:"first"` // should come from ret1 through ret2 A2 A `name:"second"` // should come from ret2 } - require.NoError(t, c.Provide(func() Ret2 { + require.NoError(t, provide(func() Ret2 { return Ret2{ Ret1: Ret1{ A1: A{1}, @@ -566,14 +572,14 @@ func TestEndToEndSuccess(t *testing.T) { A2: A{2}, } })) - require.NoError(t, c.Invoke(func(p param) { + require.NoError(t, invoke(func(p param) { assert.Equal(t, 1, p.A1.idx) assert.Equal(t, 2, p.A2.idx) }), "invoke should succeed, pulling out two named instances") }) t.Run("named instances do not cause cycles", func(t *testing.T) { - c := New() + provide, invoke := fp() type A struct{ idx int } type param struct { In @@ -594,22 +600,22 @@ func TestEndToEndSuccess(t *testing.T) { A `name:"dos"` } - require.NoError(t, c.Provide(func() retUno { + require.NoError(t, provide(func() retUno { return retUno{A: A{1}} }), `should be able to provide A[name="uno"]`) - require.NoError(t, c.Provide(func(p param) retDos { + require.NoError(t, provide(func(p param) retDos { return retDos{A: A{2}} }), `A[name="dos"] should be able to rely on A[name="uno"]`) - require.NoError(t, c.Invoke(func(p paramBoth) { + require.NoError(t, invoke(func(p paramBoth) { assert.Equal(t, 1, p.A1.idx) assert.Equal(t, 2, p.A2.idx) }), "both objects should be successfully resolved on Invoke") }) t.Run("struct constructor with as interface option", func(t *testing.T) { - c := New() + provide, invoke := fp() - provider := c.Provide( + provider := provide( func() *bytes.Buffer { var buf bytes.Buffer buf.WriteString("foo") @@ -620,7 +626,7 @@ func TestEndToEndSuccess(t *testing.T) { require.NoError(t, provider, "provide failed") - require.NoError(t, c.Invoke( + require.NoError(t, invoke( func(s fmt.Stringer, r io.Reader) { require.Equal(t, "foo", s.String(), "invoke got new buffer") got, err := ioutil.ReadAll(r) @@ -631,9 +637,9 @@ func TestEndToEndSuccess(t *testing.T) { }) t.Run("As with Name", func(t *testing.T) { - c := New() + provide, invoke := fp() - require.NoError(t, c.Provide( + require.NoError(t, provide( func() *bytes.Buffer { return bytes.NewBufferString("foo") }, @@ -648,7 +654,7 @@ func TestEndToEndSuccess(t *testing.T) { Reader io.Reader `name:"buff"` } - require.NoError(t, c.Invoke(func(got in) { + require.NoError(t, invoke(func(got in) { assert.NotNil(t, got.Buffer, "buffer must not be nil") assert.True(t, got.Buffer == got.Reader, @@ -661,21 +667,21 @@ func TestEndToEndSuccess(t *testing.T) { }) t.Run("As same interface", func(t *testing.T) { - c := New() - require.NoError(t, c.Provide(func() io.Reader { + provide, _ := fp() + require.NoError(t, provide(func() io.Reader { panic("this function should not be called") }, As(new(io.Reader))), "failed to provide") }) t.Run("As different interface", func(t *testing.T) { - c := New() - require.NoError(t, c.Provide(func() io.ReadCloser { + provide, _ := fp() + require.NoError(t, provide(func() io.ReadCloser { panic("this function should not be called") }, As(new(io.Reader), new(io.Closer))), "failed to provide") }) t.Run("invoke on a type that depends on named parameters", func(t *testing.T) { - c := New() + provide, invoke := fp() type A struct{ idx int } type B struct{ sum int } type param struct { @@ -691,16 +697,16 @@ func TestEndToEndSuccess(t *testing.T) { A1 *A `name:"foo"` A2 *A `name:"bar"` } - require.NoError(t, c.Provide(func() (ret, error) { + require.NoError(t, provide(func() (ret, error) { return ret{ A1: &A{1}, A2: &A{2}, }, nil }), "should be able to provide A1 and A2 into the graph") - require.NoError(t, c.Provide(func(p param) *B { + require.NoError(t, provide(func(p param) *B { return &B{sum: p.A1.idx + p.A2.idx} }), "should be able to provide *B that relies on two named types") - require.NoError(t, c.Invoke(func(b *B) { + require.NoError(t, invoke(func(b *B) { require.Equal(t, 3, b.sum) })) }) @@ -719,16 +725,16 @@ func TestEndToEndSuccess(t *testing.T) { } t.Run("optional", func(t *testing.T) { - c := New() + _, invoke := fp() called1 := false - require.NoError(t, c.Invoke(func(p param1) { + require.NoError(t, invoke(func(p param1) { called1 = true assert.Nil(t, p.Foo) })) called2 := false - require.NoError(t, c.Invoke(func(p param2) { + require.NoError(t, invoke(func(p param2) { called2 = true assert.Nil(t, p.Foo) })) @@ -738,20 +744,20 @@ func TestEndToEndSuccess(t *testing.T) { }) t.Run("named", func(t *testing.T) { - c := New() + provide, invoke := fp() - require.NoError(t, c.Provide(func() *struct{} { + require.NoError(t, provide(func() *struct{} { return &struct{}{} }, Name("foo"))) called1 := false - require.NoError(t, c.Invoke(func(p param1) { + require.NoError(t, invoke(func(p param1) { called1 = true assert.NotNil(t, p.Foo) })) called2 := false - require.NoError(t, c.Invoke(func(p param2) { + require.NoError(t, invoke(func(p param2) { called2 = true assert.NotNil(t, p.Foo) })) @@ -765,7 +771,7 @@ func TestEndToEndSuccess(t *testing.T) { t.Run("dynamically generated dig.In", func(t *testing.T) { // This test verifies that a dig.In generated using reflect.StructOf // works with our dig.In detection logic. - c := New() + provide, invoke := fp() type type1 struct{} type type2 struct{} @@ -777,7 +783,7 @@ func TestEndToEndSuccess(t *testing.T) { return gave } - require.NoError(t, c.Provide(new1), "failed to provide constructor") + require.NoError(t, provide(new1), "failed to provide constructor") // We generate a struct that embeds dig.In. // @@ -806,1530 +812,230 @@ func TestEndToEndSuccess(t *testing.T) { fn := reflect.MakeFunc( reflect.FuncOf([]reflect.Type{inType}, nil /* returns */, false /* variadic */), func(args []reflect.Value) []reflect.Value { - require.Len(t, args, 1, "expected only one argument") - require.Equal(t, reflect.Struct, args[0].Kind(), "argument must be a struct") - require.Equal(t, 3, args[0].NumField(), "struct must have two fields") - - t1, ok := args[0].Field(1).Interface().(*type1) - require.True(t, ok, "field must be a type1") - require.NotNil(t, t1, "value must not be nil") - require.True(t, t1 == gave, "value must match constructor's return value") - - require.True(t, args[0].Field(2).IsNil(), "type2 must be nil") - return nil - }, - ) - - require.NoError(t, c.Invoke(fn.Interface()), "invoke failed") - }) - - t.Run("dynamically generated dig.Out", func(t *testing.T) { - // This test verifies that a dig.Out generated using reflect.StructOf - // works with our dig.Out detection logic. - - c := New() - - type A struct{ Value int } - - outType := reflect.StructOf([]reflect.StructField{ - anonymousField(reflect.TypeOf(Out{})), - { - Name: "Foo", - Type: reflect.TypeOf(&A{}), - Tag: `name:"foo"`, - }, - { - Name: "Bar", - Type: reflect.TypeOf(&A{}), - Tag: `name:"bar"`, - }, - }) - - fn := reflect.MakeFunc( - reflect.FuncOf(nil /* params */, []reflect.Type{outType}, false /* variadic */), - func([]reflect.Value) []reflect.Value { - result := reflect.New(outType).Elem() - result.Field(1).Set(reflect.ValueOf(&A{Value: 1})) - result.Field(2).Set(reflect.ValueOf(&A{Value: 2})) - return []reflect.Value{result} - }, - ) - require.NoError(t, c.Provide(fn.Interface()), "provide failed") - - type params struct { - In - - Foo *A `name:"foo"` - Bar *A `name:"bar"` - Baz *A `name:"baz" optional:"true"` - } - - require.NoError(t, c.Invoke(func(p params) { - assert.Equal(t, &A{Value: 1}, p.Foo, "Foo must match") - assert.Equal(t, &A{Value: 2}, p.Bar, "Bar must match") - assert.Nil(t, p.Baz, "Baz must be unset") - }), "invoke failed") - }) - - t.Run("variadic arguments invoke", func(t *testing.T) { - c := New() - - type A struct{} - - var gaveA *A - require.NoError(t, c.Provide(func() *A { - gaveA = &A{} - return gaveA - }), "failed to provide A") - - require.NoError(t, c.Provide(func() []*A { - panic("[]*A constructor must not be called.") - }), "failed to provide A slice") - - require.NoError(t, c.Invoke(func(a *A, as ...*A) { - require.NotNil(t, a, "A must not be nil") - require.True(t, a == gaveA, "A must match") - require.Empty(t, as, "varargs must be empty") - }), "failed to invoke") - }) - - t.Run("variadic arguments dependency", func(t *testing.T) { - c := New() - - type A struct{} - type B struct{} - - var gaveA *A - require.NoError(t, c.Provide(func() *A { - gaveA = &A{} - return gaveA - }), "failed to provide A") - - require.NoError(t, c.Provide(func() []*A { - panic("[]*A constructor must not be called.") - }), "failed to provide A slice") - - var gaveB *B - require.NoError(t, c.Provide(func(a *A, as ...*A) *B { - require.NotNil(t, a, "A must not be nil") - require.True(t, a == gaveA, "A must match") - require.Empty(t, as, "varargs must be empty") - gaveB = &B{} - return gaveB - }), "failed to provide B") - - require.NoError(t, c.Invoke(func(b *B) { - require.NotNil(t, b, "B must not be nil") - require.True(t, b == gaveB, "B must match") - }), "failed to invoke") - }) - - t.Run("non-error return arguments from invoke are ignored", func(t *testing.T) { - c := New() - type A struct{} - type B struct{} - - require.NoError(t, c.Provide(func() A { return A{} })) - require.NoError(t, c.Invoke(func(A) B { return B{} })) - - err := c.Invoke(func(B) {}) - require.Error(t, err, "invoking with B param should error out") - assertErrorMatches(t, err, - `missing dependencies for function "go.uber.org/dig".TestEndToEndSuccess.func\S+ \(\S+/src/go.uber.org/dig/dig_test.go:\d+\):`, - "type dig.B is not in the container,", - "did you mean to Provide it?", - ) - }) -} - -func TestChildren(t *testing.T) { - t.Parallel() - - t.Run("pointer constructor", func(t *testing.T) { - c := New() - - ch := c.Child("child") - var b *bytes.Buffer - require.NoError(t, ch.Provide(func() *bytes.Buffer { - b = &bytes.Buffer{} - return b - }), "provide failed") - require.NoError(t, c.Invoke(func(got *bytes.Buffer) { - require.NotNil(t, got, "invoke got nil buffer") - require.True(t, got == b, "invoke got wrong buffer") - }), "invoke failed") - }) - - t.Run("nil pointer constructor", func(t *testing.T) { - // Dig shouldn't forbid this - it's perfectly reasonable to explicitly - // provide a typed nil, since that's often a convenient way to supply a - // default no-op implementation. - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() *bytes.Buffer { return nil }), "provide failed") - require.NoError(t, c.Invoke(func(b *bytes.Buffer) { - require.Nil(t, b, "expected to get nil buffer") - }), "invoke failed") - }) - - t.Run("struct constructor", func(t *testing.T) { - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() bytes.Buffer { - var buf bytes.Buffer - buf.WriteString("foo") - return buf - }), "provide failed") - require.NoError(t, c.Invoke(func(b bytes.Buffer) { - // ensure we're getting back the buffer we put in - require.Equal(t, "foo", b.String(), "invoke got new buffer") - }), "invoke failed") - }) - - t.Run("slice constructor", func(t *testing.T) { - c := New() - - ch := c.Child("child") - b1 := &bytes.Buffer{} - b2 := &bytes.Buffer{} - require.NoError(t, ch.Provide(func() []*bytes.Buffer { - return []*bytes.Buffer{b1, b2} - }), "provide failed") - require.NoError(t, c.Invoke(func(bs []*bytes.Buffer) { - require.Equal(t, 2, len(bs), "invoke got unexpected number of buffers") - require.True(t, b1 == bs[0], "first item did not match") - require.True(t, b2 == bs[1], "second item did not match") - }), "invoke failed") - }) - - t.Run("array constructor", func(t *testing.T) { - c := New() - - ch := c.Child("child") - bufs := [1]*bytes.Buffer{{}} - require.NoError(t, ch.Provide(func() [1]*bytes.Buffer { return bufs }), "provide failed") - require.NoError(t, c.Invoke(func(bs [1]*bytes.Buffer) { - require.NotNil(t, bs[0], "invoke got new array") - }), "invoke failed") - }) - - t.Run("map constructor", func(t *testing.T) { - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() map[string]string { - return map[string]string{} - }), "provide failed") - require.NoError(t, c.Invoke(func(m map[string]string) { - require.NotNil(t, m, "invoke got zero value map") - }), "invoke failed") - }) - - t.Run("channel constructor", func(t *testing.T) { - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() chan int { - return make(chan int) - }), "provide failed") - require.NoError(t, c.Invoke(func(ch chan int) { - require.NotNil(t, ch, "invoke got nil chan") - }), "invoke failed") - }) - - t.Run("func constructor", func(t *testing.T) { - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() func(int) { - return func(int) {} - }), "provide failed") - require.NoError(t, c.Invoke(func(f func(int)) { - require.NotNil(t, f, "invoke got nil function pointer") - }), "invoke failed") - }) - - t.Run("interface constructor", func(t *testing.T) { - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() io.Writer { - return &bytes.Buffer{} - }), "provide failed") - require.NoError(t, c.Invoke(func(w io.Writer) { - require.NotNil(t, w, "invoke got nil interface") - }), "invoke failed") - }) - - t.Run("param", func(t *testing.T) { - c := New() - - ch := c.Child("child") - type contents string - type Args struct { - In - - Contents contents - } - - require.NoError(t, - ch.Provide(func(args Args) *bytes.Buffer { - require.NotEmpty(t, args.Contents, "contents must not be empty") - return bytes.NewBufferString(string(args.Contents)) - }), "provide constructor failed") - - require.NoError(t, - ch.Provide(func() contents { return "hello world" }), - "provide value failed") - - require.NoError(t, c.Invoke(func(buff *bytes.Buffer) { - out, err := ioutil.ReadAll(buff) - require.NoError(t, err, "read from buffer failed") - require.Equal(t, "hello world", string(out), "contents don't match") - })) - }) - - t.Run("invoke param", func(t *testing.T) { - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() *bytes.Buffer { - return new(bytes.Buffer) - }), "provide failed") - - type Args struct { - In - - *bytes.Buffer - } - - require.NoError(t, c.Invoke(func(args Args) { - require.NotNil(t, args.Buffer, "invoke got nil buffer") - })) - }) - - t.Run("param wrapper", func(t *testing.T) { - var ( - buff *bytes.Buffer - called bool - ) - - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() *bytes.Buffer { - require.False(t, called, "constructor must be called exactly once") - called = true - buff = new(bytes.Buffer) - return buff - }), "provide failed") - - type MyParam struct{ In } - - type Args struct { - MyParam - - Buffer *bytes.Buffer - } - - require.NoError(t, c.Invoke(func(args Args) { - require.True(t, called, "constructor must be called first") - require.NotNil(t, args.Buffer, "invoke got nil buffer") - require.True(t, args.Buffer == buff, "buffer must match constructor's return value") - })) - }) - - t.Run("param recurse", func(t *testing.T) { - type anotherParam struct { - In - - Buffer *bytes.Buffer - } - - type someParam struct { - In - - Buffer *bytes.Buffer - Another anotherParam - } - - var ( - buff *bytes.Buffer - called bool - ) - - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() *bytes.Buffer { - require.False(t, called, "constructor must be called exactly once") - called = true - buff = new(bytes.Buffer) - return buff - }), "provide must not fail") - - require.NoError(t, c.Invoke(func(p someParam) { - require.True(t, called, "constructor must be called first") - - require.NotNil(t, p.Buffer, "someParam.Buffer must not be nil") - require.NotNil(t, p.Another.Buffer, "anotherParam.Buffer must not be nil") - - require.True(t, p.Buffer == p.Another.Buffer, "buffers fields must match") - require.True(t, p.Buffer == buff, "buffer must match constructor's return value") - }), "invoke must not fail") - }) - - t.Run("multiple-type constructor", func(t *testing.T) { - c := New() - - ch := c.Child("child") - constructor := func() (*bytes.Buffer, []int, error) { - return &bytes.Buffer{}, []int{42}, nil - } - consumer := func(b *bytes.Buffer, nums []int) { - assert.NotNil(t, b, "invoke got nil buffer") - assert.Equal(t, 1, len(nums), "invoke got empty slice") - } - require.NoError(t, ch.Provide(constructor), "provide failed") - require.NoError(t, c.Invoke(consumer), "invoke failed") - }) - - t.Run("multiple-type constructor is called once", func(t *testing.T) { - c := New() - - ch := c.Child("child") - type A struct{} - type B struct{} - count := 0 - constructor := func() (*A, *B, error) { - count++ - return &A{}, &B{}, nil - } - getA := func(a *A) { - assert.NotNil(t, a, "got nil A") - } - getB := func(b *B) { - assert.NotNil(t, b, "got nil B") - } - require.NoError(t, ch.Provide(constructor), "provide failed") - require.NoError(t, c.Invoke(getA), "A invoke failed") - require.NoError(t, c.Invoke(getB), "B invoke failed") - require.NoError(t, c.Invoke(func(a *A, b *B) {}), "AB invoke failed") - require.Equal(t, 1, count, "Constructor must be called once") - }) - - t.Run("method invocation inside Invoke", func(t *testing.T) { - c := New() - - ch := c.Child("child") - type A struct{} - type B struct{} - cA := func() (*A, error) { - return &A{}, nil - } - cB := func() (*B, error) { - return &B{}, nil - } - getA := func(a *A) { - c.Invoke(func(b *B) { - assert.NotNil(t, b, "got nil B") - }) - assert.NotNil(t, a, "got nil A") - } - - require.NoError(t, ch.Provide(cA), "provide failed") - require.NoError(t, ch.Provide(cB), "provide failed") - require.NoError(t, c.Invoke(getA), "A invoke failed") - }) - - t.Run("collections and instances of same type", func(t *testing.T) { - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() []*bytes.Buffer { - return []*bytes.Buffer{{}} - }), "providing collection failed") - require.NoError(t, ch.Provide(func() *bytes.Buffer { - return &bytes.Buffer{} - }), "providing pointer failed") - }) - - t.Run("optional param field", func(t *testing.T) { - type type1 struct{} - type type2 struct{} - type type3 struct{} - type type4 struct{} - type type5 struct{} - constructor := func() (*type1, *type3, *type4) { - return &type1{}, &type3{}, &type4{} - } - - c := New() - - ch := c.Child("child") - type param struct { - In - - T1 *type1 // regular 'ol type - T2 *type2 `optional:"true" useless_tag:"false"` // optional type NOT in the graph - T3 *type3 `unrelated:"foo=42, optional"` // type in the graph with unrelated tag - T4 *type4 `optional:"true"` // optional type present in the graph - T5 *type5 `optional:"t"` // optional type NOT in the graph with "yes" - } - require.NoError(t, ch.Provide(constructor)) - require.NoError(t, c.Invoke(func(p param) { - require.NotNil(t, p.T1, "whole param struct should not be nil") - assert.Nil(t, p.T2, "optional type not in the graph should return nil") - assert.NotNil(t, p.T3, "required type with unrelated tag not in the graph") - assert.NotNil(t, p.T4, "optional type in the graph should not return nil") - assert.Nil(t, p.T5, "optional type not in the graph should return nil") - })) - }) - - t.Run("out type inserts multiple objects into the graph", func(t *testing.T) { - type A struct{ name string } - type B struct{ name string } - type Ret struct { - Out - A // value type A - *B // pointer type *B - } - myA := A{"string A"} - myB := &B{"string B"} - - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() Ret { - return Ret{A: myA, B: myB} - }), "provide for the Ret struct should succeed") - require.NoError(t, c.Invoke(func(a A, b *B) { - assert.Equal(t, a.name, "string A", "value type should work for dig.Out") - assert.Equal(t, b.name, "string B", "pointer should work for dig.Out") - assert.True(t, myA == a, "should get the same pointer for &A") - assert.Equal(t, b, myB, "b and myB should be uqual") - })) - }) - - t.Run("constructor with optional", func(t *testing.T) { - type type1 struct{} - type type2 struct{} - - type param struct { - In - - T1 *type1 `optional:"true"` - } - - c := New() - - ch := c.Child("child") - - var gave *type2 - require.NoError(t, ch.Provide(func(p param) *type2 { - require.Nil(t, p.T1, "T1 must be nil") - gave = &type2{} - return gave - }), "provide failed") - - require.NoError(t, c.Invoke(func(got *type2) { - require.True(t, got == gave, "type2 reference must be the same") - }), "invoke failed") - }) - - t.Run("nested dependencies", func(t *testing.T) { - c := New() - - ch := c.Child("child") - - type A struct{ name string } - type B struct{ name string } - type C struct{ name string } - - require.NoError(t, ch.Provide(func() A { return A{"->A"} })) - require.NoError(t, ch.Provide(func(A) B { return B{"A->B"} })) - require.NoError(t, ch.Provide(func(A, B) C { return C{"AB->C"} })) - require.NoError(t, c.Invoke(func(a A, b B, c C) { - assert.Equal(t, a, A{"->A"}) - assert.Equal(t, b, B{"A->B"}) - assert.Equal(t, c, C{"AB->C"}) - }), "invoking should succeed") - }) - - t.Run("primitives", func(t *testing.T) { - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() string { return "piper" }), "string provide failed") - require.NoError(t, ch.Provide(func() int { return 42 }), "int provide failed") - require.NoError(t, ch.Provide(func() int64 { return 24 }), "int provide failed") - require.NoError(t, ch.Provide(func() time.Duration { - return 10 * time.Second - }), "time.Duration provide failed") - require.NoError(t, c.Invoke(func(i64 int64, i int, s string, d time.Duration) { - assert.Equal(t, 42, i) - assert.Equal(t, int64(24), i64) - assert.Equal(t, "piper", s) - assert.Equal(t, 10*time.Second, d) - })) - }) - - t.Run("out types recurse", func(t *testing.T) { - type A struct{} - type B struct{} - type C struct{} - // Contains A - type Ret1 struct { - Out - *A - } - // Contains *A (through Ret1), *B and C - type Ret2 struct { - Ret1 - *B - C - } - c := New() - - ch := c.Child("child") - - require.NoError(t, ch.Provide(func() Ret2 { - return Ret2{ - Ret1: Ret1{ - A: &A{}, - }, - B: &B{}, - C: C{}, - } - }), "provide for the Ret struct should succeed") - require.NoError(t, c.Invoke(func(a *A, b *B, c C) { - require.NotNil(t, a, "*A should be part of the container through Ret2->Ret1") - })) - }) - - t.Run("named instances can be created with tags", func(t *testing.T) { - c := New() - - ch := c.Child("child") - type A struct{ idx int } - - // returns three named instances of A - type ret struct { - Out - - A1 A `name:"first"` - A2 A `name:"second"` - A3 A `name:"third"` - } - - // requires two specific named instances - type param struct { - In - - A1 A `name:"first"` - A3 A `name:"third"` - } - require.NoError(t, ch.Provide(func() ret { - return ret{A1: A{1}, A2: A{2}, A3: A{3}} - }), "provide for three named instances should succeed") - require.NoError(t, c.Invoke(func(p param) { - assert.Equal(t, 1, p.A1.idx) - assert.Equal(t, 3, p.A3.idx) - }), "invoke should succeed, pulling out two named instances") - }) - - t.Run("named instances can be created with Name option", func(t *testing.T) { - c := New() - - ch := c.Child("child") - - type A struct{ idx int } - - buildConstructor := func(idx int) func() A { - return func() A { return A{idx: idx} } - } - - require.NoError(t, ch.Provide(buildConstructor(1), Name("first"))) - require.NoError(t, ch.Provide(buildConstructor(2), Name("second"))) - require.NoError(t, ch.Provide(buildConstructor(3), Name("third"))) - - type param struct { - In - - A1 A `name:"first"` - A3 A `name:"third"` - } - - require.NoError(t, c.Invoke(func(p param) { - assert.Equal(t, 1, p.A1.idx) - assert.Equal(t, 3, p.A3.idx) - }), "invoke should succeed, pulling out two named instances") - }) - - t.Run("named and unnamed instances coexist", func(t *testing.T) { - c := New() - - ch := c.Child("child") - type A struct{ idx int } - - type out struct { - Out - - A `name:"foo"` - } - - require.NoError(t, ch.Provide(func() out { return out{A: A{1}} })) - require.NoError(t, ch.Provide(func() A { return A{2} })) - - type in struct { - In - - A1 A `name:"foo"` - A2 A - } - require.NoError(t, c.Invoke(func(i in) { - assert.Equal(t, 1, i.A1.idx) - assert.Equal(t, 2, i.A2.idx) - })) - }) - - t.Run("named instances recurse", func(t *testing.T) { - c := New() - - ch := c.Child("child") - type A struct{ idx int } - - type Ret1 struct { - Out - - A1 A `name:"first"` - } - type Ret2 struct { - Ret1 - - A2 A `name:"second"` - } - type param struct { - In - - A1 A `name:"first"` // should come from ret1 through ret2 - A2 A `name:"second"` // should come from ret2 - } - require.NoError(t, ch.Provide(func() Ret2 { - return Ret2{ - Ret1: Ret1{ - A1: A{1}, - }, - A2: A{2}, - } - })) - require.NoError(t, c.Invoke(func(p param) { - assert.Equal(t, 1, p.A1.idx) - assert.Equal(t, 2, p.A2.idx) - }), "invoke should succeed, pulling out two named instances") - }) - - t.Run("named instances do not cause cycles", func(t *testing.T) { - c := New() - - ch := c.Child("child") - type A struct{ idx int } - type param struct { - In - A `name:"uno"` - } - type paramBoth struct { - In - - A1 A `name:"uno"` - A2 A `name:"dos"` - } - type retUno struct { - Out - A `name:"uno"` - } - type retDos struct { - Out - A `name:"dos"` - } - - require.NoError(t, ch.Provide(func() retUno { - return retUno{A: A{1}} - }), `should be able to provide A[name="uno"]`) - require.NoError(t, ch.Provide(func(p param) retDos { - return retDos{A: A{2}} - }), `A[name="dos"] should be able to rely on A[name="uno"]`) - require.NoError(t, c.Invoke(func(p paramBoth) { - assert.Equal(t, 1, p.A1.idx) - assert.Equal(t, 2, p.A2.idx) - }), "both objects should be successfully resolved on Invoke") - }) - - t.Run("struct constructor with as interface option", func(t *testing.T) { - c := New() - - ch := c.Child("child") - - provider := ch.Provide( - func() *bytes.Buffer { - var buf bytes.Buffer - buf.WriteString("foo") - return &buf - }, - As(new(fmt.Stringer), new(io.Reader)), - ) - - require.NoError(t, provider, "provide failed") - - require.NoError(t, c.Invoke( - func(s fmt.Stringer, r io.Reader) { - require.Equal(t, "foo", s.String(), "invoke got new buffer") - got, err := ioutil.ReadAll(r) - assert.NoError(t, err, "failed to read from reader") - require.Equal(t, "foo", string(got), "invoke got new buffer") - }, - ), "invoke failed") - }) - - t.Run("As with Name", func(t *testing.T) { - c := New() - - ch := c.Child("child") - - require.NoError(t, ch.Provide( - func() *bytes.Buffer { - return bytes.NewBufferString("foo") - }, - As(new(io.Reader)), - Name("buff"), - ), "failed to provide") - - type in struct { - In - - Buffer *bytes.Buffer `name:"buff"` - Reader io.Reader `name:"buff"` - } - - require.NoError(t, c.Invoke(func(got in) { - assert.NotNil(t, got.Buffer, "buffer must not be nil") - - assert.True(t, got.Buffer == got.Reader, - "reader and buffer must be the same object") - - body, err := ioutil.ReadAll(got.Reader) - require.NoError(t, err, "failed to read buffer body") - assert.Equal(t, "foo", string(body)) - })) - }) - - t.Run("As same interface", func(t *testing.T) { - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() io.Reader { - panic("this function should not be called") - }, As(new(io.Reader))), "failed to provide") - }) - - t.Run("As different interface", func(t *testing.T) { - c := New() - - ch := c.Child("child") - require.NoError(t, ch.Provide(func() io.ReadCloser { - panic("this function should not be called") - }, As(new(io.Reader), new(io.Closer))), "failed to provide") - }) - - t.Run("invoke on a type that depends on named parameters", func(t *testing.T) { - c := New() - - ch := c.Child("child") - type A struct{ idx int } - type B struct{ sum int } - type param struct { - In - - A1 *A `name:"foo"` - A2 *A `name:"bar"` - A3 *A `name:"baz" optional:"true"` - } - type ret struct { - Out - - A1 *A `name:"foo"` - A2 *A `name:"bar"` - } - require.NoError(t, ch.Provide(func() (ret, error) { - return ret{ - A1: &A{1}, - A2: &A{2}, - }, nil - }), "should be able to provide A1 and A2 into the graph") - require.NoError(t, ch.Provide(func(p param) *B { - return &B{sum: p.A1.idx + p.A2.idx} - }), "should be able to provide *B that relies on two named types") - require.NoError(t, c.Invoke(func(b *B) { - require.Equal(t, 3, b.sum) - })) - }) - - t.Run("optional and named ordering doesn't matter", func(t *testing.T) { - type param1 struct { - In - - Foo *struct{} `name:"foo" optional:"true"` - } - - type param2 struct { - In - - Foo *struct{} `optional:"true" name:"foo"` - } - - t.Run("optional", func(t *testing.T) { - c := New() - - ch := c.Child("child") - - called1 := false - require.NoError(t, ch.Invoke(func(p param1) { - called1 = true - assert.Nil(t, p.Foo) - })) - - called2 := false - require.NoError(t, ch.Invoke(func(p param2) { - called2 = true - assert.Nil(t, p.Foo) - })) - - assert.True(t, called1) - assert.True(t, called2) - }) - - t.Run("named", func(t *testing.T) { - c := New() - - ch := c.Child("child") - - require.NoError(t, ch.Provide(func() *struct{} { - return &struct{}{} - }, Name("foo"))) - - called1 := false - require.NoError(t, c.Invoke(func(p param1) { - called1 = true - assert.NotNil(t, p.Foo) - })) - - called2 := false - require.NoError(t, c.Invoke(func(p param2) { - called2 = true - assert.NotNil(t, p.Foo) - })) - - assert.True(t, called1) - assert.True(t, called2) - }) - - }) - - t.Run("dynamically generated dig.In", func(t *testing.T) { - // This test verifies that a dig.In generated using reflect.StructOf - // works with our dig.In detection logic. - c := New() - - ch := c.Child("child") - - type type1 struct{} - type type2 struct{} - - var gave *type1 - new1 := func() *type1 { - require.Nil(t, gave, "constructor must be called only once") - gave = &type1{} - return gave - } - - require.NoError(t, ch.Provide(new1), "failed to provide constructor") - - // We generate a struct that embeds dig.In. - // - // Note that the fix for https://github.com/golang/go/issues/18780 - // requires that StructField.Name is always set but versions of Go - // older than 1.9 expect Name to be empty for embedded fields. - // - // We use utils_for_go19_test and utils_for_pre_go19_test with build - // tags to implement this behavior differently in the two Go versions. - - inType := reflect.StructOf([]reflect.StructField{ - anonymousField(reflect.TypeOf(In{})), - { - Name: "Foo", - Type: reflect.TypeOf(&type1{}), - }, - { - Name: "Bar", - Type: reflect.TypeOf(&type2{}), - Tag: `optional:"true"`, - }, - }) - - // We generate a function that relies on that struct and validates the - // result. - fn := reflect.MakeFunc( - reflect.FuncOf([]reflect.Type{inType}, nil /* returns */, false /* variadic */), - func(args []reflect.Value) []reflect.Value { - require.Len(t, args, 1, "expected only one argument") - require.Equal(t, reflect.Struct, args[0].Kind(), "argument must be a struct") - require.Equal(t, 3, args[0].NumField(), "struct must have two fields") - - t1, ok := args[0].Field(1).Interface().(*type1) - require.True(t, ok, "field must be a type1") - require.NotNil(t, t1, "value must not be nil") - require.True(t, t1 == gave, "value must match constructor's return value") - - require.True(t, args[0].Field(2).IsNil(), "type2 must be nil") - return nil - }, - ) - - require.NoError(t, c.Invoke(fn.Interface()), "invoke failed") - }) - - t.Run("dynamically generated dig.Out", func(t *testing.T) { - // This test verifies that a dig.Out generated using reflect.StructOf - // works with our dig.Out detection logic. - - c := New() - - ch := c.Child("child") - - type A struct{ Value int } - - outType := reflect.StructOf([]reflect.StructField{ - anonymousField(reflect.TypeOf(Out{})), - { - Name: "Foo", - Type: reflect.TypeOf(&A{}), - Tag: `name:"foo"`, - }, - { - Name: "Bar", - Type: reflect.TypeOf(&A{}), - Tag: `name:"bar"`, - }, - }) - - fn := reflect.MakeFunc( - reflect.FuncOf(nil /* params */, []reflect.Type{outType}, false /* variadic */), - func([]reflect.Value) []reflect.Value { - result := reflect.New(outType).Elem() - result.Field(1).Set(reflect.ValueOf(&A{Value: 1})) - result.Field(2).Set(reflect.ValueOf(&A{Value: 2})) - return []reflect.Value{result} - }, - ) - require.NoError(t, ch.Provide(fn.Interface()), "provide failed") - - type params struct { - In - - Foo *A `name:"foo"` - Bar *A `name:"bar"` - Baz *A `name:"baz" optional:"true"` - } - - require.NoError(t, c.Invoke(func(p params) { - assert.Equal(t, &A{Value: 1}, p.Foo, "Foo must match") - assert.Equal(t, &A{Value: 2}, p.Bar, "Bar must match") - assert.Nil(t, p.Baz, "Baz must be unset") - }), "invoke failed") - }) - - t.Run("variadic arguments invoke", func(t *testing.T) { - c := New() - - ch := c.Child("child") - - type A struct{} - - var gaveA *A - require.NoError(t, ch.Provide(func() *A { - gaveA = &A{} - return gaveA - }), "failed to provide A") - - require.NoError(t, ch.Provide(func() []*A { - panic("[]*A constructor must not be called.") - }), "failed to provide A slice") - - require.NoError(t, c.Invoke(func(a *A, as ...*A) { - require.NotNil(t, a, "A must not be nil") - require.True(t, a == gaveA, "A must match") - require.Empty(t, as, "varargs must be empty") - }), "failed to invoke") - }) - - t.Run("variadic arguments dependency", func(t *testing.T) { - c := New() - - ch := c.Child("child") - - type A struct{} - type B struct{} - - var gaveA *A - require.NoError(t, ch.Provide(func() *A { - gaveA = &A{} - return gaveA - }), "failed to provide A") - - require.NoError(t, ch.Provide(func() []*A { - panic("[]*A constructor must not be called.") - }), "failed to provide A slice") - - var gaveB *B - require.NoError(t, ch.Provide(func(a *A, as ...*A) *B { - require.NotNil(t, a, "A must not be nil") - require.True(t, a == gaveA, "A must match") - require.Empty(t, as, "varargs must be empty") - gaveB = &B{} - return gaveB - }), "failed to provide B") - - require.NoError(t, c.Invoke(func(b *B) { - require.NotNil(t, b, "B must not be nil") - require.True(t, b == gaveB, "B must match") - }), "failed to invoke") - }) - - t.Run("non-error return arguments from invoke are ignored", func(t *testing.T) { - c := New() - - ch := c.Child("child") - type A struct{} - type B struct{} - - require.NoError(t, ch.Provide(func() A { return A{} })) - require.NoError(t, c.Invoke(func(A) B { return B{} })) - - err := c.Invoke(func(B) {}) - require.Error(t, err, "invoking with B param should error out") - assertErrorMatches(t, err, - `missing dependencies for function "go.uber.org/dig".TestChildren.func\S+ \(\S+/src/go.uber.org/dig/dig_test.go:\d+\):`, - "type dig.B is not in the container,", - "did you mean to Provide it?", - ) - }) - - t.Run("parent providers available to deeply nested children", func(t *testing.T) { - c := New() - - var b *bytes.Buffer - require.NoError(t, c.Provide(func() *bytes.Buffer { - b = &bytes.Buffer{} - return b - }), "provide failed") - ch := c.Child("1").Child("2").Child("3") - require.NoError(t, ch.Invoke(func(got *bytes.Buffer) { - require.NotNil(t, got, "invoke got nil buffer") - require.True(t, got == b, "invoke got wrong buffer") - }), "invoke failed") - }) - - t.Run("multiple sub graphs", func(t *testing.T) { - c := New() - - cc := make([]*Container, 0, 5) - for i := 0; i < 5; i++ { - cc = append(cc, c.Child(strconv.Itoa(i))) - } - - for i := 0; i < 5; i++ { - cc = append(cc, cc[1].Child(strconv.Itoa(i))) - } - - var b *bytes.Buffer - require.NoError(t, cc[2].Provide(func() *bytes.Buffer { - b = &bytes.Buffer{} - return b - }), "provide failed") - require.NoError(t, c.Invoke(func(got *bytes.Buffer) { - require.NotNil(t, got, "invoke got nil buffer") - require.True(t, got == b, "invoke got wrong buffer") - }), "invoke failed") - }) -} - -func TestGroups(t *testing.T) { - t.Run("empty slice received without provides", func(t *testing.T) { - c := New() - - type in struct { - In - - Values []int `group:"foo"` - } - - require.NoError(t, c.Invoke(func(i in) { - require.Empty(t, i.Values) - }), "failed to invoke") - }) - - t.Run("values are provided", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - - type out struct { - Out - - Value int `group:"val"` - } - - provide := func(i int) { - require.NoError(t, c.Provide(func() out { - return out{Value: i} - }), "failed to provide ") - } - - provide(1) - provide(2) - provide(3) - - type in struct { - In - - Values []int `group:"val"` - } - - require.NoError(t, c.Invoke(func(i in) { - assert.Equal(t, []int{2, 3, 1}, i.Values) - }), "invoke failed") - }) - - t.Run("groups are provided via option", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - - provide := func(i int) { - require.NoError(t, c.Provide(func() int { - return i - }, Group("val")), "failed to provide ") - } - - provide(1) - provide(2) - provide(3) - - type in struct { - In - - Values []int `group:"val"` - } - - require.NoError(t, c.Invoke(func(i in) { - assert.Equal(t, []int{2, 3, 1}, i.Values) - }), "invoke failed") - }) - - t.Run("different types may be grouped", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - - provide := func(i int, s string) { - require.NoError(t, c.Provide(func() (int, string) { - return i, s - }, Group("val")), "failed to provide ") - } - - provide(1, "a") - provide(2, "b") - provide(3, "c") - - type in struct { - In - - Ivalues []int `group:"val"` - Svalues []string `group:"val"` - } - - require.NoError(t, c.Invoke(func(i in) { - assert.Equal(t, []int{2, 3, 1}, i.Ivalues) - assert.Equal(t, []string{"a", "c", "b"}, i.Svalues) - }), "invoke failed") - }) - - t.Run("group options may not be provided for result structs", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - - type out struct { - Out - - Value int `group:"val"` - } - - func(i int) { - require.Error(t, c.Provide(func() { - t.Fatal("This should not be called") - }, Group("val")), "This Provide should fail") - }(1) - }) - - t.Run("constructor is called at most once", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - - type out struct { - Out - - Result string `group:"s"` - } - - calls := make(map[string]int) - - provide := func(i string) { - require.NoError(t, c.Provide(func() out { - calls[i]++ - return out{Result: i} - }), "failed to provide") - } - - provide("foo") - provide("bar") - provide("baz") - - type in struct { - In - - Results []string `group:"s"` - } - - // Expected value of in.Results in consecutive calls. - expected := [][]string{ - {"bar", "baz", "foo"}, - {"foo", "baz", "bar"}, - {"baz", "bar", "foo"}, - {"bar", "foo", "baz"}, - } + require.Len(t, args, 1, "expected only one argument") + require.Equal(t, reflect.Struct, args[0].Kind(), "argument must be a struct") + require.Equal(t, 3, args[0].NumField(), "struct must have two fields") - for i, want := range expected { - require.NoError(t, c.Invoke(func(i in) { - require.Equal(t, want, i.Results) - }), "invoke %d failed", i) - } + t1, ok := args[0].Field(1).Interface().(*type1) + require.True(t, ok, "field must be a type1") + require.NotNil(t, t1, "value must not be nil") + require.True(t, t1 == gave, "value must match constructor's return value") - for s, v := range calls { - assert.Equal(t, 1, v, "constructor for %q called too many times", s) - } + require.True(t, args[0].Field(2).IsNil(), "type2 must be nil") + return nil + }, + ) + + require.NoError(t, invoke(fn.Interface()), "invoke failed") }) - t.Run("consume groups in constructor", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) + t.Run("dynamically generated dig.Out", func(t *testing.T) { + // This test verifies that a dig.Out generated using reflect.StructOf + // works with our dig.Out detection logic. - type out struct { - Out + provide, invoke := fp() - Result []string `group:"hi"` - } + type A struct{ Value int } - provideStrings := func(strings ...string) { - require.NoError(t, c.Provide(func() out { - return out{Result: strings} - }), "failed to provide") - } + outType := reflect.StructOf([]reflect.StructField{ + anonymousField(reflect.TypeOf(Out{})), + { + Name: "Foo", + Type: reflect.TypeOf(&A{}), + Tag: `name:"foo"`, + }, + { + Name: "Bar", + Type: reflect.TypeOf(&A{}), + Tag: `name:"bar"`, + }, + }) - provideStrings("1", "2") - provideStrings("3", "4", "5") - provideStrings("6") - provideStrings("7", "8", "9", "10") + fn := reflect.MakeFunc( + reflect.FuncOf(nil /* params */, []reflect.Type{outType}, false /* variadic */), + func([]reflect.Value) []reflect.Value { + result := reflect.New(outType).Elem() + result.Field(1).Set(reflect.ValueOf(&A{Value: 1})) + result.Field(2).Set(reflect.ValueOf(&A{Value: 2})) + return []reflect.Value{result} + }, + ) + require.NoError(t, provide(fn.Interface()), "provide failed") - type setParams struct { + type params struct { In - Strings [][]string `group:"hi"` + Foo *A `name:"foo"` + Bar *A `name:"bar"` + Baz *A `name:"baz" optional:"true"` } - require.NoError(t, c.Provide(func(p setParams) map[string]struct{} { - m := make(map[string]struct{}) - for _, ss := range p.Strings { - for _, s := range ss { - m[s] = struct{}{} - } - } - return m - }), "failed to provide set constructor") - require.NoError(t, c.Invoke(func(got map[string]struct{}) { - assert.Equal(t, map[string]struct{}{ - "1": {}, "2": {}, "3": {}, "4": {}, "5": {}, - "6": {}, "7": {}, "8": {}, "9": {}, "10": {}, - }, got) - }), "failed to invoke") + require.NoError(t, invoke(func(p params) { + assert.Equal(t, &A{Value: 1}, p.Foo, "Foo must match") + assert.Equal(t, &A{Value: 2}, p.Bar, "Bar must match") + assert.Nil(t, p.Baz, "Baz must be unset") + }), "invoke failed") }) - t.Run("provide multiple values", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - - type outInt struct { - Out - Int int `group:"foo"` - } + t.Run("variadic arguments invoke", func(t *testing.T) { + provide, invoke := fp() - provideInt := func(i int) { - require.NoError(t, c.Provide(func() (outInt, error) { - return outInt{Int: i}, nil - }), "failed to provide int") - } + type A struct{} - type outString struct { - Out - String string `group:"foo"` - } + var gaveA *A + require.NoError(t, provide(func() *A { + gaveA = &A{} + return gaveA + }), "failed to provide A") - provideString := func(s string) { - require.NoError(t, c.Provide(func() outString { - return outString{String: s} - }), "failed to provide string") - } + require.NoError(t, provide(func() []*A { + panic("[]*A constructor must not be called.") + }), "failed to provide A slice") - type outBoth struct { - Out + require.NoError(t, invoke(func(a *A, as ...*A) { + require.NotNil(t, a, "A must not be nil") + require.True(t, a == gaveA, "A must match") + require.Empty(t, as, "varargs must be empty") + }), "failed to invoke") + }) - Int int `group:"foo"` - String string `group:"foo"` - } + t.Run("variadic arguments dependency", func(t *testing.T) { + provide, invoke := fp() - provideBoth := func(i int, s string) { - require.NoError(t, c.Provide(func() (outBoth, error) { - return outBoth{Int: i, String: s}, nil - }), "failed to provide both") - } + type A struct{} + type B struct{} - provideInt(1) - provideString("foo") - provideBoth(2, "bar") - provideString("baz") - provideInt(3) - provideBoth(4, "qux") - provideBoth(5, "quux") - provideInt(6) - provideInt(7) + var gaveA *A + require.NoError(t, provide(func() *A { + gaveA = &A{} + return gaveA + }), "failed to provide A") - type in struct { - In + require.NoError(t, provide(func() []*A { + panic("[]*A constructor must not be called.") + }), "failed to provide A slice") - Ints []int `group:"foo"` - Strings []string `group:"foo"` - } + var gaveB *B + require.NoError(t, provide(func(a *A, as ...*A) *B { + require.NotNil(t, a, "A must not be nil") + require.True(t, a == gaveA, "A must match") + require.Empty(t, as, "varargs must be empty") + gaveB = &B{} + return gaveB + }), "failed to provide B") - require.NoError(t, c.Invoke(func(got in) { - assert.Equal(t, in{ - Ints: []int{5, 3, 4, 1, 6, 7, 2}, - Strings: []string{"foo", "bar", "baz", "quux", "qux"}, - }, got) + require.NoError(t, invoke(func(b *B) { + require.NotNil(t, b, "B must not be nil") + require.True(t, b == gaveB, "B must match") }), "failed to invoke") }) - t.Run("duplicate values are supported", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - - type out struct { - Out - - Result string `group:"s"` - } - - provide := func(i string) { - require.NoError(t, c.Provide(func() out { - return out{Result: i} - }), "failed to provide") - } + t.Run("non-error return arguments from invoke are ignored", func(t *testing.T) { + provide, invoke := fp() + type A struct{} + type B struct{} - provide("a") - provide("b") - provide("c") - provide("a") - provide("d") - provide("d") - provide("a") - provide("e") + require.NoError(t, provide(func() A { return A{} })) + require.NoError(t, invoke(func(A) B { return B{} })) - type stringSlice []string + err := invoke(func(B) {}) + require.Error(t, err, "invoking with B param should error out") + assertErrorMatches(t, err, + `missing dependencies for function "go.uber.org/dig".testEndToEndSuccess.func\S+ \(\S+/src/go.uber.org/dig/dig_test.go:\d+\):`, + "type dig.B is not in the container,", + "did you mean to Provide it?", + ) + }) +} - type in struct { - In +func TestChildren(t *testing.T) { + t.Parallel() - Strings stringSlice `group:"s"` - } + t.Run("parent providers available to deeply nested children", func(t *testing.T) { + c := New() - require.NoError(t, c.Invoke(func(i in) { - assert.Equal(t, - stringSlice{"d", "c", "a", "a", "d", "e", "b", "a"}, - i.Strings) - }), "failed to invoke") + var b *bytes.Buffer + require.NoError(t, c.Provide(func() *bytes.Buffer { + b = &bytes.Buffer{} + return b + }), "provide failed") + ch := c.Child("1").Child("2").Child("3") + require.NoError(t, ch.Invoke(func(got *bytes.Buffer) { + require.NotNil(t, got, "invoke got nil buffer") + require.True(t, got == b, "invoke got wrong buffer") + }), "invoke failed") }) - t.Run("failure to build a grouped value fails everything", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - - type out struct { - Out + t.Run("multiple sub graphs", func(t *testing.T) { + c := New() - Result string `group:"x"` + cc := make([]*Container, 0, 5) + for i := 0; i < 5; i++ { + cc = append(cc, c.Child(strconv.Itoa(i))) } - require.NoError(t, c.Provide(func() (out, error) { - return out{Result: "foo"}, nil - }), "failed to provide") + for i := 0; i < 5; i++ { + cc = append(cc, cc[1].Child(strconv.Itoa(i))) + } - var gaveErr error - require.NoError(t, c.Provide(func() (out, error) { - gaveErr = errors.New("great sadness") - return out{}, gaveErr - }), "failed to provide") + var b *bytes.Buffer + require.NoError(t, cc[2].Provide(func() *bytes.Buffer { + b = &bytes.Buffer{} + return b + }), "provide failed") + require.NoError(t, c.Invoke(func(got *bytes.Buffer) { + require.NotNil(t, got, "invoke got nil buffer") + require.True(t, got == b, "invoke got wrong buffer") + }), "invoke failed") + }) +} - require.NoError(t, c.Provide(func() out { - return out{Result: "bar"} - }), "failed to provide") +func TestGroups(t *testing.T) { + testSubGraphs(t, testGroups) +} - type in struct { - In +func testSubGraphs(t *testing.T, tf func(*testing.T, newFunc)) { + t.Run("root", func(t *testing.T) { + tf(t, func(oo ...Option) (func(interface{}, ...ProvideOption) error, func(interface{}, ...InvokeOption) error) { + c := New(oo...) + return c.Provide, c.Invoke + }) + }) - Strings []string `group:"x"` - } + t.Run("provide in parent, invoke in child", func(t *testing.T) { + tf(t, func(oo ...Option) (func(interface{}, ...ProvideOption) error, func(interface{}, ...InvokeOption) error) { + parent := New(oo...) + child := parent.Child("child") + return parent.Provide, child.Invoke + }) + }) - err := c.Invoke(func(i in) { - require.FailNow(t, "this function must not be called") + t.Run("provide in child, invoke in parent", func(t *testing.T) { + tf(t, func(oo ...Option) (func(interface{}, ...ProvideOption) error, func(interface{}, ...InvokeOption) error) { + parent := New(oo...) + child := parent.Child("child") + return child.Provide, parent.Invoke }) - require.Error(t, err, "expected failure") - assertErrorMatches(t, err, - `could not build arguments for function "go.uber.org/dig".TestGroups`, - `could not build value group string\[group="x"\]:`, - `function "go.uber.org/dig".TestGroups\S+ \(\S+:\d+\) returned a non-nil error:`, - "great sadness", - ) - assert.Equal(t, gaveErr, RootCause(err)) }) } -func TestChildrenGroups(t *testing.T) { +func testGroups(t *testing.T, fp newFunc) { t.Run("empty slice received without provides", func(t *testing.T) { - c := New() + _, invoke := fp() - ch := c.Child("child") type in struct { In Values []int `group:"foo"` } - require.NoError(t, ch.Invoke(func(i in) { + require.NoError(t, invoke(func(i in) { require.Empty(t, i.Values) }), "failed to invoke") }) t.Run("values are provided", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - ch := c.Child("child") + provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) type out struct { Out @@ -2337,15 +1043,15 @@ func TestChildrenGroups(t *testing.T) { Value int `group:"val"` } - provide := func(i int) { - require.NoError(t, ch.Provide(func() out { + p := func(i int) { + require.NoError(t, provide(func() out { return out{Value: i} }), "failed to provide ") } - provide(1) - provide(2) - provide(3) + p(1) + p(2) + p(3) type in struct { In @@ -2353,24 +1059,23 @@ func TestChildrenGroups(t *testing.T) { Values []int `group:"val"` } - require.NoError(t, c.Invoke(func(i in) { + require.NoError(t, invoke(func(i in) { assert.Equal(t, []int{2, 3, 1}, i.Values) }), "invoke failed") }) t.Run("groups are provided via option", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - ch := c.Child("child") + provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) - provide := func(i int) { - require.NoError(t, ch.Provide(func() int { + p := func(i int) { + require.NoError(t, provide(func() int { return i }, Group("val")), "failed to provide ") } - provide(1) - provide(2) - provide(3) + p(1) + p(2) + p(3) type in struct { In @@ -2378,24 +1083,23 @@ func TestChildrenGroups(t *testing.T) { Values []int `group:"val"` } - require.NoError(t, c.Invoke(func(i in) { + require.NoError(t, invoke(func(i in) { assert.Equal(t, []int{2, 3, 1}, i.Values) }), "invoke failed") }) t.Run("different types may be grouped", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - ch := c.Child("child") + provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) - provide := func(i int, s string) { - require.NoError(t, ch.Provide(func() (int, string) { + p := func(i int, s string) { + require.NoError(t, provide(func() (int, string) { return i, s }, Group("val")), "failed to provide ") } - provide(1, "a") - provide(2, "b") - provide(3, "c") + p(1, "a") + p(2, "b") + p(3, "c") type in struct { In @@ -2404,15 +1108,14 @@ func TestChildrenGroups(t *testing.T) { Svalues []string `group:"val"` } - require.NoError(t, c.Invoke(func(i in) { + require.NoError(t, invoke(func(i in) { assert.Equal(t, []int{2, 3, 1}, i.Ivalues) assert.Equal(t, []string{"a", "c", "b"}, i.Svalues) }), "invoke failed") }) t.Run("group options may not be provided for result structs", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - ch := c.Child("child") + provide, _ := fp(setRand(rand.New(rand.NewSource(0)))) type out struct { Out @@ -2421,15 +1124,14 @@ func TestChildrenGroups(t *testing.T) { } func(i int) { - require.Error(t, ch.Provide(func() { + require.Error(t, provide(func() { t.Fatal("This should not be called") }, Group("val")), "This Provide should fail") }(1) }) t.Run("constructor is called at most once", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - ch := c.Child("child") + provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) type out struct { Out @@ -2439,16 +1141,16 @@ func TestChildrenGroups(t *testing.T) { calls := make(map[string]int) - provide := func(i string) { - require.NoError(t, ch.Provide(func() out { + p := func(i string) { + require.NoError(t, provide(func() out { calls[i]++ return out{Result: i} }), "failed to provide") } - provide("foo") - provide("bar") - provide("baz") + p("foo") + p("bar") + p("baz") type in struct { In @@ -2465,7 +1167,7 @@ func TestChildrenGroups(t *testing.T) { } for i, want := range expected { - require.NoError(t, c.Invoke(func(i in) { + require.NoError(t, invoke(func(i in) { require.Equal(t, want, i.Results) }), "invoke %d failed", i) } @@ -2476,8 +1178,7 @@ func TestChildrenGroups(t *testing.T) { }) t.Run("consume groups in constructor", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - ch := c.Child("child") + provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) type out struct { Out @@ -2486,7 +1187,7 @@ func TestChildrenGroups(t *testing.T) { } provideStrings := func(strings ...string) { - require.NoError(t, ch.Provide(func() out { + require.NoError(t, provide(func() out { return out{Result: strings} }), "failed to provide") } @@ -2501,7 +1202,7 @@ func TestChildrenGroups(t *testing.T) { Strings [][]string `group:"hi"` } - require.NoError(t, ch.Provide(func(p setParams) map[string]struct{} { + require.NoError(t, provide(func(p setParams) map[string]struct{} { m := make(map[string]struct{}) for _, ss := range p.Strings { for _, s := range ss { @@ -2511,7 +1212,7 @@ func TestChildrenGroups(t *testing.T) { return m }), "failed to provide set constructor") - require.NoError(t, c.Invoke(func(got map[string]struct{}) { + require.NoError(t, invoke(func(got map[string]struct{}) { assert.Equal(t, map[string]struct{}{ "1": {}, "2": {}, "3": {}, "4": {}, "5": {}, "6": {}, "7": {}, "8": {}, "9": {}, "10": {}, @@ -2520,8 +1221,7 @@ func TestChildrenGroups(t *testing.T) { }) t.Run("provide multiple values", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - ch := c.Child("child") + provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) type outInt struct { Out @@ -2529,7 +1229,7 @@ func TestChildrenGroups(t *testing.T) { } provideInt := func(i int) { - require.NoError(t, ch.Provide(func() (outInt, error) { + require.NoError(t, provide(func() (outInt, error) { return outInt{Int: i}, nil }), "failed to provide int") } @@ -2540,7 +1240,7 @@ func TestChildrenGroups(t *testing.T) { } provideString := func(s string) { - require.NoError(t, ch.Provide(func() outString { + require.NoError(t, provide(func() outString { return outString{String: s} }), "failed to provide string") } @@ -2553,7 +1253,7 @@ func TestChildrenGroups(t *testing.T) { } provideBoth := func(i int, s string) { - require.NoError(t, ch.Provide(func() (outBoth, error) { + require.NoError(t, provide(func() (outBoth, error) { return outBoth{Int: i, String: s}, nil }), "failed to provide both") } @@ -2575,7 +1275,7 @@ func TestChildrenGroups(t *testing.T) { Strings []string `group:"foo"` } - require.NoError(t, c.Invoke(func(got in) { + require.NoError(t, invoke(func(got in) { assert.Equal(t, in{ Ints: []int{5, 3, 4, 1, 6, 7, 2}, Strings: []string{"foo", "bar", "baz", "quux", "qux"}, @@ -2584,8 +1284,7 @@ func TestChildrenGroups(t *testing.T) { }) t.Run("duplicate values are supported", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - ch := c.Child("child") + provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) type out struct { Out @@ -2593,20 +1292,20 @@ func TestChildrenGroups(t *testing.T) { Result string `group:"s"` } - provide := func(i string) { - require.NoError(t, ch.Provide(func() out { + p := func(i string) { + require.NoError(t, provide(func() out { return out{Result: i} }), "failed to provide") } - provide("a") - provide("b") - provide("c") - provide("a") - provide("d") - provide("d") - provide("a") - provide("e") + p("a") + p("b") + p("c") + p("a") + p("d") + p("d") + p("a") + p("e") type stringSlice []string @@ -2616,7 +1315,7 @@ func TestChildrenGroups(t *testing.T) { Strings stringSlice `group:"s"` } - require.NoError(t, c.Invoke(func(i in) { + require.NoError(t, invoke(func(i in) { assert.Equal(t, stringSlice{"d", "c", "a", "a", "d", "e", "b", "a"}, i.Strings) @@ -2624,8 +1323,7 @@ func TestChildrenGroups(t *testing.T) { }) t.Run("failure to build a grouped value fails everything", func(t *testing.T) { - c := New(setRand(rand.New(rand.NewSource(0)))) - ch := c.Child("child") + provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) type out struct { Out @@ -2633,17 +1331,17 @@ func TestChildrenGroups(t *testing.T) { Result string `group:"x"` } - require.NoError(t, ch.Provide(func() (out, error) { + require.NoError(t, provide(func() (out, error) { return out{Result: "foo"}, nil }), "failed to provide") var gaveErr error - require.NoError(t, ch.Provide(func() (out, error) { + require.NoError(t, provide(func() (out, error) { gaveErr = errors.New("great sadness") return out{}, gaveErr }), "failed to provide") - require.NoError(t, ch.Provide(func() out { + require.NoError(t, provide(func() out { return out{Result: "bar"} }), "failed to provide") @@ -2653,14 +1351,14 @@ func TestChildrenGroups(t *testing.T) { Strings []string `group:"x"` } - err := c.Invoke(func(i in) { + err := invoke(func(i in) { require.FailNow(t, "this function must not be called") }) require.Error(t, err, "expected failure") assertErrorMatches(t, err, - `could not build arguments for function "go.uber.org/dig".TestChildrenGroups`, + `could not build arguments for function "go.uber.org/dig".testGroups`, `could not build value group string\[group="x"\]:`, - `function "go.uber.org/dig".TestChildrenGroups\S+ \(\S+:\d+\) returned a non-nil error:`, + `function "go.uber.org/dig".testGroups\S+ \(\S+:\d+\) returned a non-nil error:`, "great sadness", ) assert.Equal(t, gaveErr, RootCause(err)) @@ -3057,6 +1755,18 @@ func TestProvideKnownTypesFails(t *testing.T) { assert.NoError(t, c.Provide(func() *bytes.Buffer { return nil })) assert.Error(t, c.Provide(func() *bytes.Buffer { return nil })) }) + t.Run("provide constructor twice first in parent and then in child", func(t *testing.T) { + parent := New() + child := parent.Child("child") + assert.NoError(t, parent.Provide(func() *bytes.Buffer { return nil })) + assert.Error(t, child.Provide(func() *bytes.Buffer { return nil })) + }) + t.Run("provide constructor twice first in parent and then in child", func(t *testing.T) { + parent := New() + child := parent.Child("child") + assert.NoError(t, child.Provide(func() *bytes.Buffer { return nil })) + assert.Error(t, parent.Provide(func() *bytes.Buffer { return nil })) + }) } func TestProvideCycleFails(t *testing.T) { @@ -3294,11 +2004,15 @@ func TestTypeCheckingEquality(t *testing.T) { func TestInvokesUseCachedObjects(t *testing.T) { t.Parallel() - c := New() + testSubGraphs(t, testInvokesUseCachedObjects) +} + +func testInvokesUseCachedObjects(t *testing.T, fp newFunc) { + provide, invoke := fp() constructorCalls := 0 buf := &bytes.Buffer{} - require.NoError(t, c.Provide(func() *bytes.Buffer { + require.NoError(t, provide(func() *bytes.Buffer { assert.Equal(t, 0, constructorCalls, "constructor must not have been called before") constructorCalls++ return buf @@ -3306,7 +2020,7 @@ func TestInvokesUseCachedObjects(t *testing.T) { calls := 0 for i := 0; i < 3; i++ { - assert.NoError(t, c.Invoke(func(b *bytes.Buffer) { + assert.NoError(t, invoke(func(b *bytes.Buffer) { calls++ require.Equal(t, 1, constructorCalls, "constructor must be called exactly once") require.Equal(t, buf, b, "invoke got different buffer pointer") From 8530116bb04b01888d9497b7827de0761eff96ad Mon Sep 17 00:00:00 2001 From: Abhinav Gupta Date: Mon, 6 May 2019 10:32:41 -0700 Subject: [PATCH 10/25] dig_test: Simplify hierachy testing Rather than a function which returns two other functions, this changes the container hierachy tests to consume a `containerView` which has `Provide` and `Invoke` methods as fields on it. This has the effect of leaving the bulk of the test logic unchanged, only changing `New()` calls to `newContainer()`. --- dig_test.go | 437 +++++++++++++++++++++++++++------------------------- 1 file changed, 227 insertions(+), 210 deletions(-) diff --git a/dig_test.go b/dig_test.go index 31271860..ee748a7f 100644 --- a/dig_test.go +++ b/dig_test.go @@ -37,23 +37,31 @@ import ( "github.com/stretchr/testify/require" ) -type newFunc func(...Option) (func(interface{}, ...ProvideOption) error, func(interface{}, ...InvokeOption) error) +// containerView is a view of one or more containers. +// +// The provide and invoke methods may point to different Container instances. +type containerView struct { + Provide func(interface{}, ...ProvideOption) error + Invoke func(interface{}, ...InvokeOption) error +} + +type newContainerFunc func(...Option) containerView func TestEndToEndSuccess(t *testing.T) { testSubGraphs(t, testEndToEndSuccess) } -func testEndToEndSuccess(t *testing.T, fp newFunc) { +func testEndToEndSuccess(t *testing.T, newContainer newContainerFunc) { t.Parallel() t.Run("pointer constructor", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() var b *bytes.Buffer - require.NoError(t, provide(func() *bytes.Buffer { + require.NoError(t, c.Provide(func() *bytes.Buffer { b = &bytes.Buffer{} return b }), "provide failed") - require.NoError(t, invoke(func(got *bytes.Buffer) { + require.NoError(t, c.Invoke(func(got *bytes.Buffer) { require.NotNil(t, got, "invoke got nil buffer") require.True(t, got == b, "invoke got wrong buffer") }), "invoke failed") @@ -63,34 +71,34 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { // Dig shouldn't forbid this - it's perfectly reasonable to explicitly // provide a typed nil, since that's often a convenient way to supply a // default no-op implementation. - provide, invoke := fp() - require.NoError(t, provide(func() *bytes.Buffer { return nil }), "provide failed") - require.NoError(t, invoke(func(b *bytes.Buffer) { + c := newContainer() + require.NoError(t, c.Provide(func() *bytes.Buffer { return nil }), "provide failed") + require.NoError(t, c.Invoke(func(b *bytes.Buffer) { require.Nil(t, b, "expected to get nil buffer") }), "invoke failed") }) t.Run("struct constructor", func(t *testing.T) { - provide, invoke := fp() - require.NoError(t, provide(func() bytes.Buffer { + c := newContainer() + require.NoError(t, c.Provide(func() bytes.Buffer { var buf bytes.Buffer buf.WriteString("foo") return buf }), "provide failed") - require.NoError(t, invoke(func(b bytes.Buffer) { + require.NoError(t, c.Invoke(func(b bytes.Buffer) { // ensure we're getting back the buffer we put in require.Equal(t, "foo", b.String(), "invoke got new buffer") }), "invoke failed") }) t.Run("slice constructor", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() b1 := &bytes.Buffer{} b2 := &bytes.Buffer{} - require.NoError(t, provide(func() []*bytes.Buffer { + require.NoError(t, c.Provide(func() []*bytes.Buffer { return []*bytes.Buffer{b1, b2} }), "provide failed") - require.NoError(t, invoke(func(bs []*bytes.Buffer) { + require.NoError(t, c.Invoke(func(bs []*bytes.Buffer) { require.Equal(t, 2, len(bs), "invoke got unexpected number of buffers") require.True(t, b1 == bs[0], "first item did not match") require.True(t, b2 == bs[1], "second item did not match") @@ -98,56 +106,56 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { }) t.Run("array constructor", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() bufs := [1]*bytes.Buffer{{}} - require.NoError(t, provide(func() [1]*bytes.Buffer { return bufs }), "provide failed") - require.NoError(t, invoke(func(bs [1]*bytes.Buffer) { + require.NoError(t, c.Provide(func() [1]*bytes.Buffer { return bufs }), "provide failed") + require.NoError(t, c.Invoke(func(bs [1]*bytes.Buffer) { require.NotNil(t, bs[0], "invoke got new array") }), "invoke failed") }) t.Run("map constructor", func(t *testing.T) { - provide, invoke := fp() - require.NoError(t, provide(func() map[string]string { + c := newContainer() + require.NoError(t, c.Provide(func() map[string]string { return map[string]string{} }), "provide failed") - require.NoError(t, invoke(func(m map[string]string) { + require.NoError(t, c.Invoke(func(m map[string]string) { require.NotNil(t, m, "invoke got zero value map") }), "invoke failed") }) t.Run("channel constructor", func(t *testing.T) { - provide, invoke := fp() - require.NoError(t, provide(func() chan int { + c := newContainer() + require.NoError(t, c.Provide(func() chan int { return make(chan int) }), "provide failed") - require.NoError(t, invoke(func(ch chan int) { + require.NoError(t, c.Invoke(func(ch chan int) { require.NotNil(t, ch, "invoke got nil chan") }), "invoke failed") }) t.Run("func constructor", func(t *testing.T) { - provide, invoke := fp() - require.NoError(t, provide(func() func(int) { + c := newContainer() + require.NoError(t, c.Provide(func() func(int) { return func(int) {} }), "provide failed") - require.NoError(t, invoke(func(f func(int)) { + require.NoError(t, c.Invoke(func(f func(int)) { require.NotNil(t, f, "invoke got nil function pointer") }), "invoke failed") }) t.Run("interface constructor", func(t *testing.T) { - provide, invoke := fp() - require.NoError(t, provide(func() io.Writer { + c := newContainer() + require.NoError(t, c.Provide(func() io.Writer { return &bytes.Buffer{} }), "provide failed") - require.NoError(t, invoke(func(w io.Writer) { + require.NoError(t, c.Invoke(func(w io.Writer) { require.NotNil(t, w, "invoke got nil interface") }), "invoke failed") }) t.Run("param", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type contents string type Args struct { In @@ -156,16 +164,16 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { } require.NoError(t, - provide(func(args Args) *bytes.Buffer { + c.Provide(func(args Args) *bytes.Buffer { require.NotEmpty(t, args.Contents, "contents must not be empty") return bytes.NewBufferString(string(args.Contents)) }), "provide constructor failed") require.NoError(t, - provide(func() contents { return "hello world" }), + c.Provide(func() contents { return "hello world" }), "provide value failed") - require.NoError(t, invoke(func(buff *bytes.Buffer) { + require.NoError(t, c.Invoke(func(buff *bytes.Buffer) { out, err := ioutil.ReadAll(buff) require.NoError(t, err, "read from buffer failed") require.Equal(t, "hello world", string(out), "contents don't match") @@ -173,8 +181,8 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { }) t.Run("invoke param", func(t *testing.T) { - provide, invoke := fp() - require.NoError(t, provide(func() *bytes.Buffer { + c := newContainer() + require.NoError(t, c.Provide(func() *bytes.Buffer { return new(bytes.Buffer) }), "provide failed") @@ -184,7 +192,7 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { *bytes.Buffer } - require.NoError(t, invoke(func(args Args) { + require.NoError(t, c.Invoke(func(args Args) { require.NotNil(t, args.Buffer, "invoke got nil buffer") })) }) @@ -195,8 +203,8 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { called bool ) - provide, invoke := fp() - require.NoError(t, provide(func() *bytes.Buffer { + c := newContainer() + require.NoError(t, c.Provide(func() *bytes.Buffer { require.False(t, called, "constructor must be called exactly once") called = true buff = new(bytes.Buffer) @@ -211,7 +219,7 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { Buffer *bytes.Buffer } - require.NoError(t, invoke(func(args Args) { + require.NoError(t, c.Invoke(func(args Args) { require.True(t, called, "constructor must be called first") require.NotNil(t, args.Buffer, "invoke got nil buffer") require.True(t, args.Buffer == buff, "buffer must match constructor's return value") @@ -237,15 +245,15 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { called bool ) - provide, invoke := fp() - require.NoError(t, provide(func() *bytes.Buffer { + c := newContainer() + require.NoError(t, c.Provide(func() *bytes.Buffer { require.False(t, called, "constructor must be called exactly once") called = true buff = new(bytes.Buffer) return buff }), "provide must not fail") - require.NoError(t, invoke(func(p someParam) { + require.NoError(t, c.Invoke(func(p someParam) { require.True(t, called, "constructor must be called first") require.NotNil(t, p.Buffer, "someParam.Buffer must not be nil") @@ -257,7 +265,7 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { }) t.Run("multiple-type constructor", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() constructor := func() (*bytes.Buffer, []int, error) { return &bytes.Buffer{}, []int{42}, nil } @@ -265,12 +273,12 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { assert.NotNil(t, b, "invoke got nil buffer") assert.Equal(t, 1, len(nums), "invoke got empty slice") } - require.NoError(t, provide(constructor), "provide failed") - require.NoError(t, invoke(consumer), "invoke failed") + require.NoError(t, c.Provide(constructor), "provide failed") + require.NoError(t, c.Invoke(consumer), "invoke failed") }) t.Run("multiple-type constructor is called once", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type A struct{} type B struct{} count := 0 @@ -284,15 +292,15 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { getB := func(b *B) { assert.NotNil(t, b, "got nil B") } - require.NoError(t, provide(constructor), "provide failed") - require.NoError(t, invoke(getA), "A invoke failed") - require.NoError(t, invoke(getB), "B invoke failed") - require.NoError(t, invoke(func(a *A, b *B) {}), "AB invoke failed") + require.NoError(t, c.Provide(constructor), "provide failed") + require.NoError(t, c.Invoke(getA), "A invoke failed") + require.NoError(t, c.Invoke(getB), "B invoke failed") + require.NoError(t, c.Invoke(func(a *A, b *B) {}), "AB invoke failed") require.Equal(t, 1, count, "Constructor must be called once") }) t.Run("method invocation inside Invoke", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type A struct{} type B struct{} cA := func() (*A, error) { @@ -302,23 +310,23 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { return &B{}, nil } getA := func(a *A) { - invoke(func(b *B) { + c.Invoke(func(b *B) { assert.NotNil(t, b, "got nil B") }) assert.NotNil(t, a, "got nil A") } - require.NoError(t, provide(cA), "provide failed") - require.NoError(t, provide(cB), "provide failed") - require.NoError(t, invoke(getA), "A invoke failed") + require.NoError(t, c.Provide(cA), "provide failed") + require.NoError(t, c.Provide(cB), "provide failed") + require.NoError(t, c.Invoke(getA), "A invoke failed") }) t.Run("collections and instances of same type", func(t *testing.T) { - provide, _ := fp() - require.NoError(t, provide(func() []*bytes.Buffer { + c := newContainer() + require.NoError(t, c.Provide(func() []*bytes.Buffer { return []*bytes.Buffer{{}} }), "providing collection failed") - require.NoError(t, provide(func() *bytes.Buffer { + require.NoError(t, c.Provide(func() *bytes.Buffer { return &bytes.Buffer{} }), "providing pointer failed") }) @@ -333,7 +341,7 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { return &type1{}, &type3{}, &type4{} } - provide, invoke := fp() + c := newContainer() type param struct { In @@ -343,8 +351,8 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { T4 *type4 `optional:"true"` // optional type present in the graph T5 *type5 `optional:"t"` // optional type NOT in the graph with "yes" } - require.NoError(t, provide(constructor)) - require.NoError(t, invoke(func(p param) { + require.NoError(t, c.Provide(constructor)) + require.NoError(t, c.Invoke(func(p param) { require.NotNil(t, p.T1, "whole param struct should not be nil") assert.Nil(t, p.T2, "optional type not in the graph should return nil") assert.NotNil(t, p.T3, "required type with unrelated tag not in the graph") @@ -364,11 +372,11 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { myA := A{"string A"} myB := &B{"string B"} - provide, invoke := fp() - require.NoError(t, provide(func() Ret { + c := newContainer() + require.NoError(t, c.Provide(func() Ret { return Ret{A: myA, B: myB} }), "provide for the Ret struct should succeed") - require.NoError(t, invoke(func(a A, b *B) { + require.NoError(t, c.Invoke(func(a A, b *B) { assert.Equal(t, a.name, "string A", "value type should work for dig.Out") assert.Equal(t, b.name, "string B", "pointer should work for dig.Out") assert.True(t, myA == a, "should get the same pointer for &A") @@ -386,31 +394,31 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { T1 *type1 `optional:"true"` } - provide, invoke := fp() + c := newContainer() var gave *type2 - require.NoError(t, provide(func(p param) *type2 { + require.NoError(t, c.Provide(func(p param) *type2 { require.Nil(t, p.T1, "T1 must be nil") gave = &type2{} return gave }), "provide failed") - require.NoError(t, invoke(func(got *type2) { + require.NoError(t, c.Invoke(func(got *type2) { require.True(t, got == gave, "type2 reference must be the same") }), "invoke failed") }) t.Run("nested dependencies", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type A struct{ name string } type B struct{ name string } type C struct{ name string } - require.NoError(t, provide(func() A { return A{"->A"} })) - require.NoError(t, provide(func(A) B { return B{"A->B"} })) - require.NoError(t, provide(func(A, B) C { return C{"AB->C"} })) - require.NoError(t, invoke(func(a A, b B, c C) { + require.NoError(t, c.Provide(func() A { return A{"->A"} })) + require.NoError(t, c.Provide(func(A) B { return B{"A->B"} })) + require.NoError(t, c.Provide(func(A, B) C { return C{"AB->C"} })) + require.NoError(t, c.Invoke(func(a A, b B, c C) { assert.Equal(t, a, A{"->A"}) assert.Equal(t, b, B{"A->B"}) assert.Equal(t, c, C{"AB->C"}) @@ -418,14 +426,14 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { }) t.Run("primitives", func(t *testing.T) { - provide, invoke := fp() - require.NoError(t, provide(func() string { return "piper" }), "string provide failed") - require.NoError(t, provide(func() int { return 42 }), "int provide failed") - require.NoError(t, provide(func() int64 { return 24 }), "int provide failed") - require.NoError(t, provide(func() time.Duration { + c := newContainer() + require.NoError(t, c.Provide(func() string { return "piper" }), "string provide failed") + require.NoError(t, c.Provide(func() int { return 42 }), "int provide failed") + require.NoError(t, c.Provide(func() int64 { return 24 }), "int provide failed") + require.NoError(t, c.Provide(func() time.Duration { return 10 * time.Second }), "time.Duration provide failed") - require.NoError(t, invoke(func(i64 int64, i int, s string, d time.Duration) { + require.NoError(t, c.Invoke(func(i64 int64, i int, s string, d time.Duration) { assert.Equal(t, 42, i) assert.Equal(t, int64(24), i64) assert.Equal(t, "piper", s) @@ -448,9 +456,9 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { *B C } - provide, invoke := fp() + c := newContainer() - require.NoError(t, provide(func() Ret2 { + require.NoError(t, c.Provide(func() Ret2 { return Ret2{ Ret1: Ret1{ A: &A{}, @@ -459,13 +467,13 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { C: C{}, } }), "provide for the Ret struct should succeed") - require.NoError(t, invoke(func(a *A, b *B, c C) { + require.NoError(t, c.Invoke(func(a *A, b *B, c C) { require.NotNil(t, a, "*A should be part of the container through Ret2->Ret1") })) }) t.Run("named instances can be created with tags", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type A struct{ idx int } // returns three named instances of A @@ -484,17 +492,17 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { A1 A `name:"first"` A3 A `name:"third"` } - require.NoError(t, provide(func() ret { + require.NoError(t, c.Provide(func() ret { return ret{A1: A{1}, A2: A{2}, A3: A{3}} }), "provide for three named instances should succeed") - require.NoError(t, invoke(func(p param) { + require.NoError(t, c.Invoke(func(p param) { assert.Equal(t, 1, p.A1.idx) assert.Equal(t, 3, p.A3.idx) }), "invoke should succeed, pulling out two named instances") }) t.Run("named instances can be created with Name option", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type A struct{ idx int } @@ -502,9 +510,9 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { return func() A { return A{idx: idx} } } - require.NoError(t, provide(buildConstructor(1), Name("first"))) - require.NoError(t, provide(buildConstructor(2), Name("second"))) - require.NoError(t, provide(buildConstructor(3), Name("third"))) + require.NoError(t, c.Provide(buildConstructor(1), Name("first"))) + require.NoError(t, c.Provide(buildConstructor(2), Name("second"))) + require.NoError(t, c.Provide(buildConstructor(3), Name("third"))) type param struct { In @@ -513,14 +521,14 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { A3 A `name:"third"` } - require.NoError(t, invoke(func(p param) { + require.NoError(t, c.Invoke(func(p param) { assert.Equal(t, 1, p.A1.idx) assert.Equal(t, 3, p.A3.idx) }), "invoke should succeed, pulling out two named instances") }) t.Run("named and unnamed instances coexist", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type A struct{ idx int } type out struct { @@ -529,8 +537,8 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { A `name:"foo"` } - require.NoError(t, provide(func() out { return out{A: A{1}} })) - require.NoError(t, provide(func() A { return A{2} })) + require.NoError(t, c.Provide(func() out { return out{A: A{1}} })) + require.NoError(t, c.Provide(func() A { return A{2} })) type in struct { In @@ -538,14 +546,14 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { A1 A `name:"foo"` A2 A } - require.NoError(t, invoke(func(i in) { + require.NoError(t, c.Invoke(func(i in) { assert.Equal(t, 1, i.A1.idx) assert.Equal(t, 2, i.A2.idx) })) }) t.Run("named instances recurse", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type A struct{ idx int } type Ret1 struct { @@ -564,7 +572,7 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { A1 A `name:"first"` // should come from ret1 through ret2 A2 A `name:"second"` // should come from ret2 } - require.NoError(t, provide(func() Ret2 { + require.NoError(t, c.Provide(func() Ret2 { return Ret2{ Ret1: Ret1{ A1: A{1}, @@ -572,14 +580,14 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { A2: A{2}, } })) - require.NoError(t, invoke(func(p param) { + require.NoError(t, c.Invoke(func(p param) { assert.Equal(t, 1, p.A1.idx) assert.Equal(t, 2, p.A2.idx) }), "invoke should succeed, pulling out two named instances") }) t.Run("named instances do not cause cycles", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type A struct{ idx int } type param struct { In @@ -600,22 +608,22 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { A `name:"dos"` } - require.NoError(t, provide(func() retUno { + require.NoError(t, c.Provide(func() retUno { return retUno{A: A{1}} }), `should be able to provide A[name="uno"]`) - require.NoError(t, provide(func(p param) retDos { + require.NoError(t, c.Provide(func(p param) retDos { return retDos{A: A{2}} }), `A[name="dos"] should be able to rely on A[name="uno"]`) - require.NoError(t, invoke(func(p paramBoth) { + require.NoError(t, c.Invoke(func(p paramBoth) { assert.Equal(t, 1, p.A1.idx) assert.Equal(t, 2, p.A2.idx) }), "both objects should be successfully resolved on Invoke") }) t.Run("struct constructor with as interface option", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() - provider := provide( + provider := c.Provide( func() *bytes.Buffer { var buf bytes.Buffer buf.WriteString("foo") @@ -626,7 +634,7 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { require.NoError(t, provider, "provide failed") - require.NoError(t, invoke( + require.NoError(t, c.Invoke( func(s fmt.Stringer, r io.Reader) { require.Equal(t, "foo", s.String(), "invoke got new buffer") got, err := ioutil.ReadAll(r) @@ -637,9 +645,9 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { }) t.Run("As with Name", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() - require.NoError(t, provide( + require.NoError(t, c.Provide( func() *bytes.Buffer { return bytes.NewBufferString("foo") }, @@ -654,7 +662,7 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { Reader io.Reader `name:"buff"` } - require.NoError(t, invoke(func(got in) { + require.NoError(t, c.Invoke(func(got in) { assert.NotNil(t, got.Buffer, "buffer must not be nil") assert.True(t, got.Buffer == got.Reader, @@ -667,21 +675,21 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { }) t.Run("As same interface", func(t *testing.T) { - provide, _ := fp() - require.NoError(t, provide(func() io.Reader { + c := newContainer() + require.NoError(t, c.Provide(func() io.Reader { panic("this function should not be called") }, As(new(io.Reader))), "failed to provide") }) t.Run("As different interface", func(t *testing.T) { - provide, _ := fp() - require.NoError(t, provide(func() io.ReadCloser { + c := newContainer() + require.NoError(t, c.Provide(func() io.ReadCloser { panic("this function should not be called") }, As(new(io.Reader), new(io.Closer))), "failed to provide") }) t.Run("invoke on a type that depends on named parameters", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type A struct{ idx int } type B struct{ sum int } type param struct { @@ -697,16 +705,16 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { A1 *A `name:"foo"` A2 *A `name:"bar"` } - require.NoError(t, provide(func() (ret, error) { + require.NoError(t, c.Provide(func() (ret, error) { return ret{ A1: &A{1}, A2: &A{2}, }, nil }), "should be able to provide A1 and A2 into the graph") - require.NoError(t, provide(func(p param) *B { + require.NoError(t, c.Provide(func(p param) *B { return &B{sum: p.A1.idx + p.A2.idx} }), "should be able to provide *B that relies on two named types") - require.NoError(t, invoke(func(b *B) { + require.NoError(t, c.Invoke(func(b *B) { require.Equal(t, 3, b.sum) })) }) @@ -725,16 +733,16 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { } t.Run("optional", func(t *testing.T) { - _, invoke := fp() + c := newContainer() called1 := false - require.NoError(t, invoke(func(p param1) { + require.NoError(t, c.Invoke(func(p param1) { called1 = true assert.Nil(t, p.Foo) })) called2 := false - require.NoError(t, invoke(func(p param2) { + require.NoError(t, c.Invoke(func(p param2) { called2 = true assert.Nil(t, p.Foo) })) @@ -744,20 +752,20 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { }) t.Run("named", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() - require.NoError(t, provide(func() *struct{} { + require.NoError(t, c.Provide(func() *struct{} { return &struct{}{} }, Name("foo"))) called1 := false - require.NoError(t, invoke(func(p param1) { + require.NoError(t, c.Invoke(func(p param1) { called1 = true assert.NotNil(t, p.Foo) })) called2 := false - require.NoError(t, invoke(func(p param2) { + require.NoError(t, c.Invoke(func(p param2) { called2 = true assert.NotNil(t, p.Foo) })) @@ -771,7 +779,7 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { t.Run("dynamically generated dig.In", func(t *testing.T) { // This test verifies that a dig.In generated using reflect.StructOf // works with our dig.In detection logic. - provide, invoke := fp() + c := newContainer() type type1 struct{} type type2 struct{} @@ -783,7 +791,7 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { return gave } - require.NoError(t, provide(new1), "failed to provide constructor") + require.NoError(t, c.Provide(new1), "failed to provide constructor") // We generate a struct that embeds dig.In. // @@ -826,14 +834,14 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { }, ) - require.NoError(t, invoke(fn.Interface()), "invoke failed") + require.NoError(t, c.Invoke(fn.Interface()), "invoke failed") }) t.Run("dynamically generated dig.Out", func(t *testing.T) { // This test verifies that a dig.Out generated using reflect.StructOf // works with our dig.Out detection logic. - provide, invoke := fp() + c := newContainer() type A struct{ Value int } @@ -860,7 +868,7 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { return []reflect.Value{result} }, ) - require.NoError(t, provide(fn.Interface()), "provide failed") + require.NoError(t, c.Provide(fn.Interface()), "provide failed") type params struct { In @@ -870,7 +878,7 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { Baz *A `name:"baz" optional:"true"` } - require.NoError(t, invoke(func(p params) { + require.NoError(t, c.Invoke(func(p params) { assert.Equal(t, &A{Value: 1}, p.Foo, "Foo must match") assert.Equal(t, &A{Value: 2}, p.Bar, "Bar must match") assert.Nil(t, p.Baz, "Baz must be unset") @@ -878,21 +886,21 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { }) t.Run("variadic arguments invoke", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type A struct{} var gaveA *A - require.NoError(t, provide(func() *A { + require.NoError(t, c.Provide(func() *A { gaveA = &A{} return gaveA }), "failed to provide A") - require.NoError(t, provide(func() []*A { + require.NoError(t, c.Provide(func() []*A { panic("[]*A constructor must not be called.") }), "failed to provide A slice") - require.NoError(t, invoke(func(a *A, as ...*A) { + require.NoError(t, c.Invoke(func(a *A, as ...*A) { require.NotNil(t, a, "A must not be nil") require.True(t, a == gaveA, "A must match") require.Empty(t, as, "varargs must be empty") @@ -900,23 +908,23 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { }) t.Run("variadic arguments dependency", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type A struct{} type B struct{} var gaveA *A - require.NoError(t, provide(func() *A { + require.NoError(t, c.Provide(func() *A { gaveA = &A{} return gaveA }), "failed to provide A") - require.NoError(t, provide(func() []*A { + require.NoError(t, c.Provide(func() []*A { panic("[]*A constructor must not be called.") }), "failed to provide A slice") var gaveB *B - require.NoError(t, provide(func(a *A, as ...*A) *B { + require.NoError(t, c.Provide(func(a *A, as ...*A) *B { require.NotNil(t, a, "A must not be nil") require.True(t, a == gaveA, "A must match") require.Empty(t, as, "varargs must be empty") @@ -924,21 +932,21 @@ func testEndToEndSuccess(t *testing.T, fp newFunc) { return gaveB }), "failed to provide B") - require.NoError(t, invoke(func(b *B) { + require.NoError(t, c.Invoke(func(b *B) { require.NotNil(t, b, "B must not be nil") require.True(t, b == gaveB, "B must match") }), "failed to invoke") }) t.Run("non-error return arguments from invoke are ignored", func(t *testing.T) { - provide, invoke := fp() + c := newContainer() type A struct{} type B struct{} - require.NoError(t, provide(func() A { return A{} })) - require.NoError(t, invoke(func(A) B { return B{} })) + require.NoError(t, c.Provide(func() A { return A{} })) + require.NoError(t, c.Invoke(func(A) B { return B{} })) - err := invoke(func(B) {}) + err := c.Invoke(func(B) {}) require.Error(t, err, "invoking with B param should error out") assertErrorMatches(t, err, `missing dependencies for function "go.uber.org/dig".testEndToEndSuccess.func\S+ \(\S+/src/go.uber.org/dig/dig_test.go:\d+\):`, @@ -994,34 +1002,43 @@ func TestGroups(t *testing.T) { testSubGraphs(t, testGroups) } -func testSubGraphs(t *testing.T, tf func(*testing.T, newFunc)) { +func testSubGraphs(t *testing.T, tf func(*testing.T, newContainerFunc)) { t.Run("root", func(t *testing.T) { - tf(t, func(oo ...Option) (func(interface{}, ...ProvideOption) error, func(interface{}, ...InvokeOption) error) { + tf(t, func(oo ...Option) containerView { c := New(oo...) - return c.Provide, c.Invoke + return containerView{ + Provide: c.Provide, + Invoke: c.Invoke, + } }) }) t.Run("provide in parent, invoke in child", func(t *testing.T) { - tf(t, func(oo ...Option) (func(interface{}, ...ProvideOption) error, func(interface{}, ...InvokeOption) error) { + tf(t, func(oo ...Option) containerView { parent := New(oo...) child := parent.Child("child") - return parent.Provide, child.Invoke + return containerView{ + Provide: parent.Provide, + Invoke: child.Invoke, + } }) }) t.Run("provide in child, invoke in parent", func(t *testing.T) { - tf(t, func(oo ...Option) (func(interface{}, ...ProvideOption) error, func(interface{}, ...InvokeOption) error) { + tf(t, func(oo ...Option) containerView { parent := New(oo...) child := parent.Child("child") - return child.Provide, parent.Invoke + return containerView{ + Provide: child.Provide, + Invoke: parent.Invoke, + } }) }) } -func testGroups(t *testing.T, fp newFunc) { +func testGroups(t *testing.T, newContainer newContainerFunc) { t.Run("empty slice received without provides", func(t *testing.T) { - _, invoke := fp() + c := newContainer() type in struct { In @@ -1029,13 +1046,13 @@ func testGroups(t *testing.T, fp newFunc) { Values []int `group:"foo"` } - require.NoError(t, invoke(func(i in) { + require.NoError(t, c.Invoke(func(i in) { require.Empty(t, i.Values) }), "failed to invoke") }) t.Run("values are provided", func(t *testing.T) { - provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) + c := newContainer(setRand(rand.New(rand.NewSource(0)))) type out struct { Out @@ -1043,15 +1060,15 @@ func testGroups(t *testing.T, fp newFunc) { Value int `group:"val"` } - p := func(i int) { - require.NoError(t, provide(func() out { + provide := func(i int) { + require.NoError(t, c.Provide(func() out { return out{Value: i} }), "failed to provide ") } - p(1) - p(2) - p(3) + provide(1) + provide(2) + provide(3) type in struct { In @@ -1059,23 +1076,23 @@ func testGroups(t *testing.T, fp newFunc) { Values []int `group:"val"` } - require.NoError(t, invoke(func(i in) { + require.NoError(t, c.Invoke(func(i in) { assert.Equal(t, []int{2, 3, 1}, i.Values) }), "invoke failed") }) t.Run("groups are provided via option", func(t *testing.T) { - provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) + c := newContainer(setRand(rand.New(rand.NewSource(0)))) - p := func(i int) { - require.NoError(t, provide(func() int { + provide := func(i int) { + require.NoError(t, c.Provide(func() int { return i }, Group("val")), "failed to provide ") } - p(1) - p(2) - p(3) + provide(1) + provide(2) + provide(3) type in struct { In @@ -1083,23 +1100,23 @@ func testGroups(t *testing.T, fp newFunc) { Values []int `group:"val"` } - require.NoError(t, invoke(func(i in) { + require.NoError(t, c.Invoke(func(i in) { assert.Equal(t, []int{2, 3, 1}, i.Values) }), "invoke failed") }) t.Run("different types may be grouped", func(t *testing.T) { - provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) + c := newContainer(setRand(rand.New(rand.NewSource(0)))) - p := func(i int, s string) { - require.NoError(t, provide(func() (int, string) { + provide := func(i int, s string) { + require.NoError(t, c.Provide(func() (int, string) { return i, s }, Group("val")), "failed to provide ") } - p(1, "a") - p(2, "b") - p(3, "c") + provide(1, "a") + provide(2, "b") + provide(3, "c") type in struct { In @@ -1108,14 +1125,14 @@ func testGroups(t *testing.T, fp newFunc) { Svalues []string `group:"val"` } - require.NoError(t, invoke(func(i in) { + require.NoError(t, c.Invoke(func(i in) { assert.Equal(t, []int{2, 3, 1}, i.Ivalues) assert.Equal(t, []string{"a", "c", "b"}, i.Svalues) }), "invoke failed") }) t.Run("group options may not be provided for result structs", func(t *testing.T) { - provide, _ := fp(setRand(rand.New(rand.NewSource(0)))) + c := newContainer(setRand(rand.New(rand.NewSource(0)))) type out struct { Out @@ -1124,14 +1141,14 @@ func testGroups(t *testing.T, fp newFunc) { } func(i int) { - require.Error(t, provide(func() { + require.Error(t, c.Provide(func() { t.Fatal("This should not be called") }, Group("val")), "This Provide should fail") }(1) }) t.Run("constructor is called at most once", func(t *testing.T) { - provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) + c := newContainer(setRand(rand.New(rand.NewSource(0)))) type out struct { Out @@ -1141,16 +1158,16 @@ func testGroups(t *testing.T, fp newFunc) { calls := make(map[string]int) - p := func(i string) { - require.NoError(t, provide(func() out { + provide := func(i string) { + require.NoError(t, c.Provide(func() out { calls[i]++ return out{Result: i} }), "failed to provide") } - p("foo") - p("bar") - p("baz") + provide("foo") + provide("bar") + provide("baz") type in struct { In @@ -1167,7 +1184,7 @@ func testGroups(t *testing.T, fp newFunc) { } for i, want := range expected { - require.NoError(t, invoke(func(i in) { + require.NoError(t, c.Invoke(func(i in) { require.Equal(t, want, i.Results) }), "invoke %d failed", i) } @@ -1178,7 +1195,7 @@ func testGroups(t *testing.T, fp newFunc) { }) t.Run("consume groups in constructor", func(t *testing.T) { - provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) + c := newContainer(setRand(rand.New(rand.NewSource(0)))) type out struct { Out @@ -1187,7 +1204,7 @@ func testGroups(t *testing.T, fp newFunc) { } provideStrings := func(strings ...string) { - require.NoError(t, provide(func() out { + require.NoError(t, c.Provide(func() out { return out{Result: strings} }), "failed to provide") } @@ -1202,7 +1219,7 @@ func testGroups(t *testing.T, fp newFunc) { Strings [][]string `group:"hi"` } - require.NoError(t, provide(func(p setParams) map[string]struct{} { + require.NoError(t, c.Provide(func(p setParams) map[string]struct{} { m := make(map[string]struct{}) for _, ss := range p.Strings { for _, s := range ss { @@ -1212,7 +1229,7 @@ func testGroups(t *testing.T, fp newFunc) { return m }), "failed to provide set constructor") - require.NoError(t, invoke(func(got map[string]struct{}) { + require.NoError(t, c.Invoke(func(got map[string]struct{}) { assert.Equal(t, map[string]struct{}{ "1": {}, "2": {}, "3": {}, "4": {}, "5": {}, "6": {}, "7": {}, "8": {}, "9": {}, "10": {}, @@ -1221,7 +1238,7 @@ func testGroups(t *testing.T, fp newFunc) { }) t.Run("provide multiple values", func(t *testing.T) { - provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) + c := newContainer(setRand(rand.New(rand.NewSource(0)))) type outInt struct { Out @@ -1229,7 +1246,7 @@ func testGroups(t *testing.T, fp newFunc) { } provideInt := func(i int) { - require.NoError(t, provide(func() (outInt, error) { + require.NoError(t, c.Provide(func() (outInt, error) { return outInt{Int: i}, nil }), "failed to provide int") } @@ -1240,7 +1257,7 @@ func testGroups(t *testing.T, fp newFunc) { } provideString := func(s string) { - require.NoError(t, provide(func() outString { + require.NoError(t, c.Provide(func() outString { return outString{String: s} }), "failed to provide string") } @@ -1253,7 +1270,7 @@ func testGroups(t *testing.T, fp newFunc) { } provideBoth := func(i int, s string) { - require.NoError(t, provide(func() (outBoth, error) { + require.NoError(t, c.Provide(func() (outBoth, error) { return outBoth{Int: i, String: s}, nil }), "failed to provide both") } @@ -1275,7 +1292,7 @@ func testGroups(t *testing.T, fp newFunc) { Strings []string `group:"foo"` } - require.NoError(t, invoke(func(got in) { + require.NoError(t, c.Invoke(func(got in) { assert.Equal(t, in{ Ints: []int{5, 3, 4, 1, 6, 7, 2}, Strings: []string{"foo", "bar", "baz", "quux", "qux"}, @@ -1284,7 +1301,7 @@ func testGroups(t *testing.T, fp newFunc) { }) t.Run("duplicate values are supported", func(t *testing.T) { - provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) + c := newContainer(setRand(rand.New(rand.NewSource(0)))) type out struct { Out @@ -1292,20 +1309,20 @@ func testGroups(t *testing.T, fp newFunc) { Result string `group:"s"` } - p := func(i string) { - require.NoError(t, provide(func() out { + provide := func(i string) { + require.NoError(t, c.Provide(func() out { return out{Result: i} }), "failed to provide") } - p("a") - p("b") - p("c") - p("a") - p("d") - p("d") - p("a") - p("e") + provide("a") + provide("b") + provide("c") + provide("a") + provide("d") + provide("d") + provide("a") + provide("e") type stringSlice []string @@ -1315,7 +1332,7 @@ func testGroups(t *testing.T, fp newFunc) { Strings stringSlice `group:"s"` } - require.NoError(t, invoke(func(i in) { + require.NoError(t, c.Invoke(func(i in) { assert.Equal(t, stringSlice{"d", "c", "a", "a", "d", "e", "b", "a"}, i.Strings) @@ -1323,7 +1340,7 @@ func testGroups(t *testing.T, fp newFunc) { }) t.Run("failure to build a grouped value fails everything", func(t *testing.T) { - provide, invoke := fp(setRand(rand.New(rand.NewSource(0)))) + c := newContainer(setRand(rand.New(rand.NewSource(0)))) type out struct { Out @@ -1331,17 +1348,17 @@ func testGroups(t *testing.T, fp newFunc) { Result string `group:"x"` } - require.NoError(t, provide(func() (out, error) { + require.NoError(t, c.Provide(func() (out, error) { return out{Result: "foo"}, nil }), "failed to provide") var gaveErr error - require.NoError(t, provide(func() (out, error) { + require.NoError(t, c.Provide(func() (out, error) { gaveErr = errors.New("great sadness") return out{}, gaveErr }), "failed to provide") - require.NoError(t, provide(func() out { + require.NoError(t, c.Provide(func() out { return out{Result: "bar"} }), "failed to provide") @@ -1351,7 +1368,7 @@ func testGroups(t *testing.T, fp newFunc) { Strings []string `group:"x"` } - err := invoke(func(i in) { + err := c.Invoke(func(i in) { require.FailNow(t, "this function must not be called") }) require.Error(t, err, "expected failure") @@ -2007,12 +2024,12 @@ func TestInvokesUseCachedObjects(t *testing.T) { testSubGraphs(t, testInvokesUseCachedObjects) } -func testInvokesUseCachedObjects(t *testing.T, fp newFunc) { - provide, invoke := fp() +func testInvokesUseCachedObjects(t *testing.T, newContainer newContainerFunc) { + c := newContainer() constructorCalls := 0 buf := &bytes.Buffer{} - require.NoError(t, provide(func() *bytes.Buffer { + require.NoError(t, c.Provide(func() *bytes.Buffer { assert.Equal(t, 0, constructorCalls, "constructor must not have been called before") constructorCalls++ return buf @@ -2020,7 +2037,7 @@ func testInvokesUseCachedObjects(t *testing.T, fp newFunc) { calls := 0 for i := 0; i < 3; i++ { - assert.NoError(t, invoke(func(b *bytes.Buffer) { + assert.NoError(t, c.Invoke(func(b *bytes.Buffer) { calls++ require.Equal(t, 1, constructorCalls, "constructor must be called exactly once") require.Equal(t, buf, b, "invoke got different buffer pointer") From d0426c02c49563ecee8ed748087ff2459133d9e8 Mon Sep 17 00:00:00 2001 From: Sri Krishna Paritala Date: Tue, 28 May 2019 10:27:15 +0530 Subject: [PATCH 11/25] Update Stringer Implementation --- stringer.go | 11 +++++++++++ stringer_test.go | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/stringer.go b/stringer.go index d10fa0fb..8411f3b3 100644 --- a/stringer.go +++ b/stringer.go @@ -29,6 +29,9 @@ import ( // String representation of the entire Container func (c *Container) String() string { b := &bytes.Buffer{} + if c.parent != nil { + fmt.Fprintf(b, "parent: %p\n", c.parent) + } fmt.Fprintln(b, "nodes: {") for k, vs := range c.providers { for _, v := range vs { @@ -48,6 +51,14 @@ func (c *Container) String() string { } fmt.Fprintln(b, "}") + fmt.Fprintln(b, "children: [") + for _, v := range c.children { + fmt.Fprintln(b, "\t{") + fmt.Fprintln(b, "\t\t", v.name, "->", v) + fmt.Fprintln(b, "\t}") + } + fmt.Fprintln(b, "]") + return b.String() } diff --git a/stringer_test.go b/stringer_test.go index e096bb6e..c1af8121 100644 --- a/stringer_test.go +++ b/stringer_test.go @@ -21,6 +21,7 @@ package dig import ( + "fmt" "math/rand" "testing" @@ -105,4 +106,14 @@ func TestStringer(t *testing.T) { assert.Contains(t, s, `string[group="baz"] => foo`) assert.Contains(t, s, `string[group="baz"] => bar`) assert.Contains(t, s, `string[group="baz"] => baz`) + + s = c.Child("child").String() + + // Parent + assert.Contains(t, s, fmt.Sprintf("parent: %p", c)) + + s = c.String() + // Children + assert.Contains(t, s, "children: [") + assert.Contains(t, s, "child -> ") } From b70979defd149338db1f5e8bfb71c003c41be804 Mon Sep 17 00:00:00 2001 From: paullen Date: Fri, 21 Jun 2019 16:36:22 +0530 Subject: [PATCH 12/25] Added Initial Support for Decorators --- .idea/dig.iml | 8 ++ .idea/misc.xml | 6 + .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + .idea/workspace.xml | 327 ++++++++++++++++++++++++++++++++++++++++++++ dig.go | 157 +++++++++++++++++++++ param.go | 41 ++++++ 7 files changed, 553 insertions(+) create mode 100644 .idea/dig.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml diff --git a/.idea/dig.iml b/.idea/dig.iml new file mode 100644 index 00000000..c956989b --- /dev/null +++ b/.idea/dig.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..28a804d8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..00547f92 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..97490158 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Run + New + Provide + provide + knownTypes + getValue + Child + newParam + NewParam + key + Name + getGroupProviders + getProviders + Container + findAnd + Decorate + Invoke + getValueProviders + BuildList + Build + getNativeDecorators + paramList + Call + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From ea3bdf0115c52360adfd2151695ee87abea9a1fc Mon Sep 17 00:00:00 2001 From: paullen Date: Fri, 6 Dec 2019 16:04:22 +0530 Subject: [PATCH 21/25] Remove print logs --- dig.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dig.go b/dig.go index 4b97bb10..a28eee9e 100644 --- a/dig.go +++ b/dig.go @@ -723,7 +723,6 @@ func (c *Container) decorate(dtor interface{}, opts provideOptions) error { } } } - //fmt.Println("Passed return-nil test") if foundNum < expectedNum { return errors.New("the result types, with the exception of error, must be present among the input parameters") } @@ -731,14 +730,12 @@ func (c *Container) decorate(dtor interface{}, opts provideOptions) error { if err != nil { return err } - //fmt.Println("checking... shallow dependency", pl) if err := shallowCheckDependencies(c.getRoot(), pl); err != nil { return errMissingDependencies{ Func: digreflect.InspectFunc(dtor), Reason: err, } } - //fmt.Println("checked shallow dependency") n, err := newNode( dtor, nodeOptions{ @@ -750,7 +747,6 @@ func (c *Container) decorate(dtor interface{}, opts provideOptions) error { if err != nil { return nil } - //fmt.Println("running findAndValidateResults...") keys, err := c.findAndValidateResults(n, true) if err != nil { return err @@ -783,7 +779,6 @@ func (c *Container) decorate(dtor interface{}, opts provideOptions) error { return errors.New("decorator must be declared in the scope of the node's container or its ancestors')") } } - fmt.Println(c.decorators) return nil } From 3efded4be01003cb6b543631e05a70b24ee834d6 Mon Sep 17 00:00:00 2001 From: paullen Date: Fri, 12 Jun 2020 17:58:54 +0530 Subject: [PATCH 22/25] Decorator InOut fix --- dig.go | 45 ++++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/dig.go b/dig.go index a28eee9e..8d78b8d0 100644 --- a/dig.go +++ b/dig.go @@ -205,6 +205,7 @@ func As(i ...interface{}) ProvideOption { type InvokeOption interface { unimplemented() } + // Container is a directed acyclic graph of types and their dependencies. type Container struct { // Mapping from key to all the nodes that can provide a value for that @@ -311,11 +312,11 @@ type provider interface { // New constructs a Container. func New(opts ...Option) *Container { c := &Container{ - providers: make(map[key][]*node), - values: make(map[key]reflect.Value), - groups: make(map[key][]reflect.Value), + providers: make(map[key][]*node), + values: make(map[key]reflect.Value), + groups: make(map[key][]reflect.Value), decorators: make(map[key][]*node), - rand: rand.New(rand.NewSource(time.Now().UnixNano())), + rand: rand.New(rand.NewSource(time.Now().UnixNano())), } for _, opt := range opts { @@ -379,7 +380,7 @@ func (c *Container) setValue(name string, t reflect.Type, v reflect.Value) { func (c *Container) getValueGroup(name string, t reflect.Type) ([]reflect.Value, bool) { items, ok := c.groups[key{group: name, t: t}] - if!ok { + if !ok { return []reflect.Value{}, ok } // shuffle the list so users don't rely on the ordering of grouped values @@ -553,7 +554,7 @@ func (c *Container) Invoke(function interface{}, opts ...InvokeOption) error { return nil } -func (c *Container) Decorate(decorator interface{}, opts ...ProvideOption) error{ +func (c *Container) Decorate(decorator interface{}, opts ...ProvideOption) error { dtype := reflect.TypeOf(decorator) if dtype == nil { return errors.New("can't decorate with an untyped nil") @@ -588,13 +589,13 @@ func (c *Container) Decorate(decorator interface{}, opts ...ProvideOption) error // does not have to be unique across different children of the container. func (c *Container) Child(name string) *Container { child := &Container{ - providers: make(map[key][]*node), - values: make(map[key]reflect.Value), - groups: make(map[key][]reflect.Value), + providers: make(map[key][]*node), + values: make(map[key]reflect.Value), + groups: make(map[key][]reflect.Value), decorators: make(map[key][]*node), - rand: c.rand, - name: name, - parent: c, + rand: c.rand, + name: name, + parent: c, } c.children = append(c.children, child) @@ -690,7 +691,12 @@ func (c *Container) decorate(dtor interface{}, opts provideOptions) error { for j := 0; j < in.NumField(); j++ { // Exclude dig.In field. if in.Field(j).Type != _inType { - inTypes[fmt.Sprintf("%v", in.Field(j).Type)] = 1 + t := in.Field(j).Type + // Exclude fx.In field + if t.Kind() == reflect.Struct && t.Field(0).Type == _inType { + continue + } + inTypes[fmt.Sprintf("%v", t)] = 1 } } } else { @@ -710,8 +716,13 @@ func (c *Container) decorate(dtor interface{}, opts provideOptions) error { for j := 0; j < out.NumField(); j++ { // Exclude dig.Out field. if out.Field(j).Type != _outType { + t := out.Field(j).Type + // Exclude fx.Out field + if t.Kind() == reflect.Struct && t.Field(0).Type == _outType { + continue + } expectedNum++ - if _, ok := inTypes[fmt.Sprintf("%v", out.Field(j).Type)]; ok { + if _, ok := inTypes[fmt.Sprintf("%v", t)]; ok { foundNum++ } } @@ -723,7 +734,7 @@ func (c *Container) decorate(dtor interface{}, opts provideOptions) error { } } } - if foundNum < expectedNum { + if foundNum < expectedNum { return errors.New("the result types, with the exception of error, must be present among the input parameters") } pl, err := newParamList(dtype) @@ -1045,8 +1056,8 @@ func shallowCheckDependencies(c containerStore, p param) error { // stagingContainerWriter is a containerWriter that records the changes that // would be made to a containerWriter and defers them until Commit is called. type stagingContainerWriter struct { - values map[key]reflect.Value - groups map[key][]reflect.Value + values map[key]reflect.Value + groups map[key][]reflect.Value isDecorated map[key]bool } From abb977f5b9128d415b7a5bb9965725d3ef8f394f Mon Sep 17 00:00:00 2001 From: paullen Date: Sun, 26 Jul 2020 01:04:12 +0530 Subject: [PATCH 23/25] check cycles in decorators --- .idea/.gitignore | 8 ++ .idea/dig.iml | 8 ++ .idea/misc.xml | 6 ++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 ++ dig.go | 185 ++++++++++++++++++++++++---------------------- result.go | 10 +-- 7 files changed, 138 insertions(+), 93 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/dig.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..73f69e09 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/dig.iml b/.idea/dig.iml new file mode 100644 index 00000000..c956989b --- /dev/null +++ b/.idea/dig.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..28a804d8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..00547f92 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/dig.go b/dig.go index 8d78b8d0..9a0bc724 100644 --- a/dig.go +++ b/dig.go @@ -572,7 +572,6 @@ func (c *Container) Decorate(decorator interface{}, opts ...ProvideOption) error } if err := c.decorate(decorator, options); err != nil { - //panic("error in running decorate()") return errConstructorFailed{ Func: digreflect.InspectFunc(decorator), Reason: err, @@ -628,7 +627,7 @@ func (c *Container) provide(ctor interface{}, opts provideOptions) error { return err } - keys, err := c.findAndValidateResults(n, false) + keys, err := c.findAndValidateResults(n) if err != nil { return err } @@ -659,7 +658,7 @@ func (c *Container) provide(ctor interface{}, opts provideOptions) error { } // Builds a collection of all result types produced by this node. -func (c *Container) findAndValidateResults(n *node, isDecorator bool) (map[key]struct{}, error) { +func (c *Container) findAndValidateResults(n *node) (map[key]struct{}, error) { var err error keyPaths := make(map[key]string) walkResult(n.ResultList(), connectionVisitor{ @@ -667,7 +666,7 @@ func (c *Container) findAndValidateResults(n *node, isDecorator bool) (map[key]s n: n, err: &err, keyPaths: keyPaths, - }, isDecorator) + }) if err != nil { return nil, err @@ -681,94 +680,100 @@ func (c *Container) findAndValidateResults(n *node, isDecorator bool) (map[key]s } func (c *Container) decorate(dtor interface{}, opts provideOptions) error { + n, err := newNode( + dtor, + nodeOptions{ + ResultName: opts.Name, + ResultGroup: opts.Group, + ResultAs: opts.As, + }, + ) + if err != nil { + return err + } + dtype := reflect.TypeOf(dtor) - // Verifying if output is present among the input params. - inTypes := make(map[string]int) + // Check if all the result types exist among the input types + inTypes := make(map[key]struct{}) for i := 0; i < dtype.NumIn(); i++ { in := dtype.In(i) - if in.Kind() == reflect.Struct { + if IsIn(in) { for j := 0; j < in.NumField(); j++ { - // Exclude dig.In field. - if in.Field(j).Type != _inType { - t := in.Field(j).Type - // Exclude fx.In field - if t.Kind() == reflect.Struct && t.Field(0).Type == _inType { - continue + t := in.Field(j).Type + //Exclude embedded In type + if IsIn(t) { + continue + } + name := in.Field(j).Tag.Get(_nameTag) + group := in.Field(j).Tag.Get(_groupTag) + if name != "" && group != "" { + return errors.New("cannot use name tags and group tags together") + } + if group != "" { + if _, ok := inTypes[key{t.Elem(), name, group}]; ok { + return fmt.Errorf("cannot provide %v multple times in decorator", t) + } + inTypes[key{t.Elem(), name, group}] = struct{}{} + } else { + if _, ok := inTypes[key{t, name, group}]; ok { + return fmt.Errorf("cannot provide %v multple times in decorator", t) } - inTypes[fmt.Sprintf("%v", t)] = 1 + inTypes[key{t, name, group}] = struct{}{} } } } else { - inTypes[fmt.Sprintf("%v", in)] = 1 + inTypes[key{t: in}] = struct{}{} } } - - foundNum := 0 - expectedNum := 0 + outTypes := make(map[key]struct{}) for i := 0; i < dtype.NumOut(); i++ { out := dtype.Out(i) - // Assuming error is at the end of the return types - if out == reflect.TypeOf((*error)(nil)).Elem() { - break - } - if out.Kind() == reflect.Struct { + if IsOut(out) { for j := 0; j < out.NumField(); j++ { - // Exclude dig.Out field. - if out.Field(j).Type != _outType { - t := out.Field(j).Type - // Exclude fx.Out field - if t.Kind() == reflect.Struct && t.Field(0).Type == _outType { - continue - } - expectedNum++ - if _, ok := inTypes[fmt.Sprintf("%v", t)]; ok { - foundNum++ - } + t := out.Field(j).Type + //Exclude embedded Out type + if IsOut(t) { + continue + } + name := out.Field(j).Tag.Get(_nameTag) + group := out.Field(j).Tag.Get(_groupTag) + if name != "" && group != "" { + return errors.New("cannot use name tags and group tags together") + } + if _, ok := outTypes[key{t, name, group}]; ok { + return fmt.Errorf("cannot provide %v multple times in decorator", t) } + outTypes[key{t, name, group}] = struct{}{} } } else { - expectedNum++ - if _, ok := inTypes[fmt.Sprintf("%v", out)]; ok { - foundNum++ - } + outTypes[key{t: out}] = struct{}{} } } - if foundNum < expectedNum { - return errors.New("the result types, with the exception of error, must be present among the input parameters") - } - pl, err := newParamList(dtype) - if err != nil { - return err - } - if err := shallowCheckDependencies(c.getRoot(), pl); err != nil { - return errMissingDependencies{ - Func: digreflect.InspectFunc(dtor), - Reason: err, + + for k := range outTypes { + if _, ok := inTypes[k]; !ok { + return errors.New("the result types, with the exception of error, must be present among the input parameters") } + delete(inTypes, k) } - n, err := newNode( - dtor, - nodeOptions{ - ResultName: opts.Name, - ResultGroup: opts.Group, - ResultAs: opts.As, - }, - ) - if err != nil { - return nil - } - keys, err := c.findAndValidateResults(n, true) - if err != nil { - return err + + params := []param{} + for k := range inTypes { + if k.group != "" { + params = append(params, paramGroupedSlice{k.group, reflect.SliceOf(k.t)}) + } else { + params = append(params, paramSingle{ + Name: k.name, + Type: k.t, + }) + } } - for k := range keys { + + for k := range outTypes { found := false // Checking for the decorator output's existence in the sub graph with the // current container as root. - if k.group != "" { - k.t = k.t.Elem() - } if _, ok := c.providers[k]; !ok { var cont []*Container cont = append(cont, c.children...) @@ -779,16 +784,33 @@ func (c *Container) decorate(dtor interface{}, opts provideOptions) error { cont = append(cont, v.children...) } else { found = true - c.decorators[k] = append(c.decorators[k], n) } } } else { found = true - c.decorators[k] = append(c.decorators[k], n) } if !found { return errors.New("decorator must be declared in the scope of the node's container or its ancestors')") } + + if len(params) > 0 { + c.isVerifiedAcyclic = false + oldParams := n.paramList.Params + oldProviders := c.providers[k] + for _, p := range c.providers[k] { + params = append(params, p.paramList.Params...) + } + n.paramList.Params = params + c.providers[k] = append([]*node{n}, c.providers[k]...) + if err := verifyAcyclic(c.getRoot(), n, k); err != nil { + c.providers[k] = oldProviders + return err + } + c.providers[k] = oldProviders + n.paramList.Params = oldParams + c.isVerifiedAcyclic = true + } + c.decorators[k] = append(c.decorators[k], n) } return nil } @@ -837,7 +859,7 @@ func (cv connectionVisitor) AnnotateWithPosition(i int) resultVisitor { return cv } -func (cv connectionVisitor) Visit(res result, isDecorator bool) resultVisitor { +func (cv connectionVisitor) Visit(res result) resultVisitor { // Already failed. Stop looking. if *cv.err != nil { return nil @@ -850,29 +872,16 @@ func (cv connectionVisitor) Visit(res result, isDecorator bool) resultVisitor { case resultSingle: k := key{name: r.Name, t: r.Type} if err := cv.checkKey(k, path); err != nil { - if !isDecorator { - *cv.err = err - return nil - } - } else { - if isDecorator { - *cv.err = fmt.Errorf("the type %v must be provided to decorate", r.Type) - return nil - } + *cv.err = err + return nil } + cv.keyPaths[k] = path for _, asType := range r.As { k := key{name: r.Name, t: asType} if err := cv.checkKey(k, path); err != nil { - if !isDecorator { - *cv.err = err - return nil - } - } else { - if isDecorator { - *cv.err = fmt.Errorf("the type %v must be provided to decorate", r.Type) - return nil - } + *cv.err = err + return nil } cv.keyPaths[k] = path } diff --git a/result.go b/result.go index 8794edf7..226b8c7a 100644 --- a/result.go +++ b/result.go @@ -96,7 +96,7 @@ type resultVisitor interface { // // If Visit returns a non-nil resultVisitor, that resultVisitor visits all // the child results of this result. - Visit(result, bool) resultVisitor + Visit(result) resultVisitor // AnnotateWithField is called on each field of a resultObject after // visiting it but before walking its descendants. @@ -129,8 +129,8 @@ type resultVisitor interface { // descendants of that resultObject/resultList. // // This is very similar to how go/ast.Walk works. -func walkResult(r result, v resultVisitor, isDecorator bool) { - v = v.Visit(r, isDecorator) +func walkResult(r result, v resultVisitor) { + v = v.Visit(r) if v == nil { return } @@ -142,14 +142,14 @@ func walkResult(r result, v resultVisitor, isDecorator bool) { w := v for _, f := range res.Fields { if v := w.AnnotateWithField(f); v != nil { - walkResult(f.Result, v, isDecorator) + walkResult(f.Result, v) } } case resultList: w := v for i, r := range res.Results { if v := w.AnnotateWithPosition(i); v != nil { - walkResult(r, v, isDecorator) + walkResult(r, v) } } default: From 2a71c7c9cfc761071a3e5297a30409a4403e6a64 Mon Sep 17 00:00:00 2001 From: paullen Date: Sun, 26 Jul 2020 01:06:10 +0530 Subject: [PATCH 24/25] gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 23249243..cf4f3388 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /vendor /.bench +.idea/ +.vscode/ *.mem *.cpu *.test From 2a1ca430a9f0b777507215b9cf8e3a045e4ba0f1 Mon Sep 17 00:00:00 2001 From: paullen Date: Sun, 26 Jul 2020 01:07:36 +0530 Subject: [PATCH 25/25] ignore dirs --- .idea/.gitignore | 8 -------- .idea/dig.iml | 8 -------- .idea/misc.xml | 6 ------ .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ 5 files changed, 36 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/dig.iml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 73f69e09..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/dig.iml b/.idea/dig.iml deleted file mode 100644 index c956989b..00000000 --- a/.idea/dig.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 28a804d8..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 00547f92..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file