cosmos-sdk/depinject/container_test.go

747 lines
14 KiB
Go

package depinject_test
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/require"
"gotest.tools/v3/golden"
"cosmossdk.io/depinject"
)
type KVStoreKey struct {
name string
}
type MsgClientA struct {
key string
}
type KeeperA struct {
key KVStoreKey
name string
}
type KeeperB struct {
key KVStoreKey
msgClientA MsgClientA
}
type KeeperC struct {
key KVStoreKey
msgClientA MsgClientA
}
type KeeperD struct {
key KVStoreKey
}
type Handler struct {
Handle func()
}
func (Handler) IsOnePerModuleType() {}
type Command struct {
Run func()
}
func (Command) IsManyPerContainerType() {}
func ProvideKVStoreKey(moduleKey depinject.ModuleKey) KVStoreKey {
return KVStoreKey{name: moduleKey.Name()}
}
func ProvideMsgClientA(key depinject.ModuleKey) MsgClientA {
return MsgClientA{key.Name()}
}
type ModuleA struct{}
func (ModuleA) Provide(key KVStoreKey, moduleKey depinject.OwnModuleKey) (KeeperA, Handler, Command) {
return KeeperA{key: key, name: depinject.ModuleKey(moduleKey).Name()}, Handler{}, Command{}
}
type ModuleB struct{}
type BDependencies struct {
depinject.In
Key KVStoreKey
A MsgClientA
}
type BProvides struct {
depinject.Out
KeeperB KeeperB
Commands []Command
}
func (ModuleB) Provide(dependencies BDependencies) (BProvides, Handler, error) {
return BProvides{
KeeperB: KeeperB{
key: dependencies.Key,
msgClientA: dependencies.A,
},
Commands: []Command{{}, {}},
}, Handler{}, nil
}
type ModuleUnexportedDependency struct{}
func (ModuleUnexportedDependency) Provide(dependencies UnexportedFieldCDependencies) (CProvides, Handler, error) {
return CProvides{
KeeperC: KeeperC{
key: dependencies.key,
msgClientA: dependencies.A,
},
Commands: []Command{{}, {}},
}, Handler{}, nil
}
type UnexportedFieldCDependencies struct {
depinject.In
key KVStoreKey
A MsgClientA
}
type CProvides struct {
depinject.Out
KeeperC KeeperC
Commands []Command
}
type ModuleUnexportedProvides struct{}
type CDependencies struct {
depinject.In
Key KVStoreKey
A MsgClientA
}
type UnexportedFieldCProvides struct {
depinject.Out
keeperC KeeperC
Commands []Command
}
func (ModuleUnexportedProvides) Provide(dependencies CDependencies) (UnexportedFieldCProvides, Handler, error) {
return UnexportedFieldCProvides{
keeperC: KeeperC{
key: dependencies.Key,
msgClientA: dependencies.A,
},
Commands: []Command{{}, {}},
}, Handler{}, nil
}
type ModuleD struct{}
type DDependencies struct {
depinject.In
Key KVStoreKey
KeeperC KeeperC
}
type DProvides struct {
depinject.Out
KeeperD KeeperD
Commands []Command
}
func (ModuleD) Provide(dependencies DDependencies) (DProvides, Handler, error) {
return DProvides{
KeeperD: KeeperD{
key: dependencies.Key,
},
Commands: []Command{{}, {}},
}, Handler{}, nil
}
func TestUnexportedField(t *testing.T) {
var (
handlers map[string]Handler
commands []Command
a KeeperA
c KeeperC
d KeeperD
scenarioConfigProvides = depinject.Configs(
depinject.Provide(ProvideMsgClientA),
depinject.ProvideInModule("runtime", ProvideKVStoreKey),
depinject.ProvideInModule("a", ModuleA.Provide),
depinject.ProvideInModule("c", ModuleUnexportedProvides.Provide),
depinject.Supply(ModuleA{}, ModuleUnexportedProvides{}),
)
scenarioConfigDependency = depinject.Configs(
depinject.Provide(ProvideMsgClientA),
depinject.ProvideInModule("runtime", ProvideKVStoreKey),
depinject.ProvideInModule("a", ModuleA.Provide),
depinject.ProvideInModule("c", ModuleUnexportedDependency.Provide),
depinject.Supply(ModuleA{}, ModuleUnexportedDependency{}),
)
scenarioConfigProvidesDependency = depinject.Configs(
depinject.Provide(ProvideMsgClientA),
depinject.ProvideInModule("runtime", ProvideKVStoreKey),
depinject.ProvideInModule("a", ModuleA.Provide),
depinject.ProvideInModule("c", ModuleUnexportedProvides.Provide),
depinject.ProvideInModule("d", ModuleD.Provide),
depinject.Supply(ModuleA{}, ModuleUnexportedProvides{}, ModuleD{}),
)
)
require.ErrorContains(t,
depinject.Inject(
scenarioConfigProvides,
&handlers,
&commands,
&a,
&c,
),
"depinject.Out struct",
)
require.ErrorContains(t,
depinject.Inject(
scenarioConfigDependency,
&handlers,
&commands,
&a,
&c,
),
"depinject.In struct",
)
require.ErrorContains(t,
depinject.Inject(
scenarioConfigProvidesDependency,
&handlers,
&commands,
&a,
&c,
&d,
),
"depinject.Out struct",
)
}
var scenarioConfig = depinject.Configs(
depinject.Provide(ProvideMsgClientA),
depinject.ProvideInModule("runtime", ProvideKVStoreKey),
depinject.ProvideInModule("a", ModuleA.Provide),
depinject.ProvideInModule("b", ModuleB.Provide),
depinject.Supply(ModuleA{}, ModuleB{}),
)
func TestScenario(t *testing.T) {
var (
handlers map[string]Handler
commands []Command
a KeeperA
b KeeperB
)
require.NoError(t,
depinject.Inject(
scenarioConfig,
&handlers,
&commands,
&a,
&b,
))
require.Len(t, handlers, 2)
require.Equal(t, Handler{}, handlers["a"])
require.Equal(t, Handler{}, handlers["b"])
require.Len(t, commands, 3)
require.Equal(t, KeeperA{
key: KVStoreKey{name: "a"},
name: "a",
}, a)
require.Equal(t, KeeperB{
key: KVStoreKey{name: "b"},
msgClientA: MsgClientA{
key: "b",
},
}, b)
}
func TestResolveError(t *testing.T) {
var x string
require.Error(t, depinject.Inject(
depinject.Provide(
func(x float64) string { return fmt.Sprintf("%f", x) },
func(x int) float64 { return float64(x) },
func(x float32) int { return int(x) },
),
&x,
))
}
func TestCyclic(t *testing.T) {
var x string
require.Error(t, depinject.Inject(
depinject.Provide(
func(x int) float64 { return float64(x) },
func(x float64) (int, string) { return int(x), "hi" },
),
&x,
))
}
func TestErrorOption(t *testing.T) {
err := depinject.Inject(depinject.Error(fmt.Errorf("an error")))
require.Error(t, err)
}
func TestTrivial(t *testing.T) {
require.NoError(t, depinject.Inject(depinject.Configs()))
}
func Provide0() int { return 0 }
func Provide1() int { return 1 }
func TestSimple(t *testing.T) {
var x int
require.NoError(t,
depinject.Inject(
depinject.Provide(Provide1),
&x,
),
)
require.Error(t,
depinject.Inject(
depinject.Provide(Provide0, Provide1),
&x,
),
)
}
func ProvideModuleScoped0(depinject.ModuleKey) int { return 0 }
func ProvideModuleScoped1(depinject.ModuleKey) int { return 1 }
func ProvideFloat64FromInt(x int) float64 { return float64(x) }
func ProvideFloat32FromInt(x int) float32 { return float32(x) }
func TestModuleScoped(t *testing.T) {
var x int
require.Error(t,
depinject.Inject(
depinject.Provide(
ProvideModuleScoped0,
),
&x,
),
)
var y float64
require.Error(t,
depinject.Inject(
depinject.Configs(
depinject.Provide(
ProvideModuleScoped0,
Provide1,
),
depinject.ProvideInModule("a", ProvideFloat64FromInt),
),
&y,
),
)
require.Error(t,
depinject.Inject(
depinject.Configs(
depinject.Provide(
Provide0,
ProvideModuleScoped0,
),
depinject.ProvideInModule("a", ProvideFloat64FromInt),
),
&y,
),
)
require.Error(t,
depinject.Inject(
depinject.Configs(
depinject.Provide(
ProvideModuleScoped0,
ProvideModuleScoped1,
),
depinject.ProvideInModule("a", ProvideFloat64FromInt),
),
&y,
),
)
require.NoError(t,
depinject.Inject(
depinject.Configs(
depinject.Provide(ProvideModuleScoped0),
depinject.ProvideInModule("a", ProvideFloat64FromInt),
),
&y,
),
)
require.Error(t,
depinject.Inject(
depinject.Configs(
depinject.Provide(ProvideModuleScoped0),
depinject.ProvideInModule("", ProvideFloat64FromInt),
),
&y,
),
)
var z float32
require.NoError(t,
depinject.Inject(
depinject.Configs(
depinject.Provide(ProvideModuleScoped0),
depinject.ProvideInModule("a",
ProvideFloat64FromInt,
ProvideFloat32FromInt,
),
),
&y, &z,
),
"use module dep twice",
)
}
type OnePerModuleInt int
func (OnePerModuleInt) IsOnePerModuleType() {}
func OnePerModuleInt3() OnePerModuleInt { return 3 }
func OnePerModuleInt4() OnePerModuleInt { return 4 }
func CollectOnePerModuleInts(x map[string]OnePerModuleInt) string {
sum := 0
for _, v := range x {
sum += int(v)
}
return fmt.Sprintf("%d", sum)
}
func ReturnOnePerModuleMap() map[string]OnePerModuleInt { return nil }
func TestOnePerModule(t *testing.T) {
var x OnePerModuleInt
require.Error(t,
depinject.Inject(depinject.Configs(), &x),
"bad input type",
)
var y map[string]OnePerModuleInt
var z string
require.NoError(t,
depinject.Inject(
depinject.Configs(
depinject.ProvideInModule("a", OnePerModuleInt3),
depinject.ProvideInModule("b", OnePerModuleInt4),
depinject.Provide(CollectOnePerModuleInts),
),
&y,
&z,
),
)
require.Equal(t, map[string]OnePerModuleInt{
"a": 3,
"b": 4,
}, y)
require.Equal(t, "7", z)
var m map[string]OnePerModuleInt
require.Error(t,
depinject.Inject(
depinject.ProvideInModule("a",
OnePerModuleInt3,
OnePerModuleInt3,
),
&m,
),
"duplicate",
)
require.Error(t,
depinject.Inject(
depinject.Provide(
OnePerModuleInt3,
),
&m,
),
"out of scope",
)
require.Error(t,
depinject.Inject(
depinject.Provide(ReturnOnePerModuleMap),
&m,
),
"bad return type",
)
require.NoError(t,
depinject.Inject(depinject.Configs(), &m),
"no providers",
)
}
type ManyPerContainerInt int
func (ManyPerContainerInt) IsManyPerContainerType() {}
func ManyPerContainerInt4() ManyPerContainerInt { return 4 }
func ManyPerContainerInt9() ManyPerContainerInt { return 9 }
func CollectManyPerContainerInts(xs []ManyPerContainerInt) string {
sum := 0
for _, x := range xs {
sum += int(x)
}
return fmt.Sprintf("%d", sum)
}
func TestManyPerContainer(t *testing.T) {
var xs []ManyPerContainerInt
var sum string
require.NoError(t,
depinject.Inject(
depinject.Provide(
ManyPerContainerInt4, ManyPerContainerInt9,
CollectManyPerContainerInts,
),
&xs,
&sum,
),
)
require.Len(t, xs, 2)
require.Contains(t, xs, ManyPerContainerInt(4))
require.Contains(t, xs, ManyPerContainerInt(9))
require.Equal(t, "13", sum)
var z ManyPerContainerInt
require.Error(t,
depinject.Inject(depinject.Provide(ManyPerContainerInt4), &z),
"bad input type",
)
require.NoError(t,
depinject.Inject(
depinject.Configs(),
&xs,
),
"no providers",
)
}
func TestSupply(t *testing.T) {
var x int
require.NoError(t,
depinject.Inject(
depinject.Supply(3),
&x,
),
)
require.Equal(t, 3, x)
require.Error(t,
depinject.Inject(
depinject.Configs(
depinject.Supply(3),
depinject.Provide(func() int { return 4 }),
),
&x,
),
"can't supply then provide",
)
require.Error(t,
depinject.Inject(
depinject.Configs(
depinject.Supply(3),
depinject.Provide(func() int { return 4 }),
),
&x,
),
"can't provide then supply",
)
require.Error(t,
depinject.Inject(
depinject.Supply(3, 4),
&x,
),
"can't supply twice",
)
}
type TestInput struct {
depinject.In
X int `optional:"true"`
Y float64
}
type TestOutput struct {
depinject.Out
X string
Y int64
}
func ProvideTestOutput() (TestOutput, error) {
return TestOutput{X: "A", Y: -10}, nil
}
func ProvideTestOutputErr() (TestOutput, error) {
return TestOutput{}, fmt.Errorf("error")
}
func TestStructArgs(t *testing.T) {
var input TestInput
require.Error(t, depinject.Inject(depinject.Configs(), &input))
require.NoError(t, depinject.Inject(
depinject.Supply(1.3),
&input,
))
require.Equal(t, 0, input.X)
require.Equal(t, 1.3, input.Y)
require.NoError(t, depinject.Inject(
depinject.Supply(1.3, 1),
&input,
))
require.Equal(t, 1, input.X)
require.Equal(t, 1.3, input.Y)
var x string
var y int64
require.NoError(t, depinject.Inject(
depinject.Provide(ProvideTestOutput),
&x, &y,
))
require.Equal(t, "A", x)
require.Equal(t, int64(-10), y)
require.Error(t, depinject.Inject(
depinject.Provide(ProvideTestOutputErr),
&x,
))
}
func TestDebugOptions(t *testing.T) {
var logOut string
var dotGraph string
outfile, err := os.CreateTemp("", "out")
require.NoError(t, err)
stdout := os.Stdout
os.Stdout = outfile
defer func() { os.Stdout = stdout }()
defer func() {
err := os.Remove(outfile.Name())
if err != nil {
panic(err)
}
}()
graphfile, err := os.CreateTemp("", "graph")
require.NoError(t, err)
defer func() {
err := os.Remove(graphfile.Name())
if err != nil {
panic(err)
}
}()
require.NoError(t, depinject.InjectDebug(
depinject.DebugOptions(
depinject.Logger(func(s string) {
logOut += s
}),
depinject.Visualizer(func(g string) {
dotGraph = g
}),
depinject.LogVisualizer(),
depinject.FileVisualizer(graphfile.Name()),
depinject.StdoutLogger(),
),
depinject.Configs(),
))
require.Contains(t, logOut, "digraph")
require.Contains(t, dotGraph, "digraph")
outfileContents, err := os.ReadFile(outfile.Name())
require.NoError(t, err)
require.Contains(t, string(outfileContents), "digraph")
graphfileContents, err := os.ReadFile(graphfile.Name())
require.NoError(t, err)
require.Contains(t, string(graphfileContents), "digraph")
}
func TestGraphAndLogOutput(t *testing.T) {
var graphOut string
var b KeeperB
debugOpts := depinject.DebugOptions(
depinject.Visualizer(func(dotGraph string) {
graphOut = dotGraph
}))
require.NoError(t, depinject.InjectDebug(debugOpts, scenarioConfig, &b))
golden.Assert(t, graphOut, "example.dot")
badConfig := depinject.Configs(
depinject.ProvideInModule("runtime", ProvideKVStoreKey),
depinject.ProvideInModule("a", ModuleA.Provide),
depinject.ProvideInModule("b", ModuleB.Provide),
)
require.Error(t, depinject.InjectDebug(debugOpts, badConfig, &b))
golden.Assert(t, graphOut, "example_error.dot")
}
func TestConditionalDebugging(t *testing.T) {
logs := ""
success := false
conditionalDebugOpt := depinject.DebugOptions(
depinject.OnError(depinject.Logger(func(s string) {
logs += s + "\n"
})),
depinject.OnSuccess(depinject.DebugCleanup(func() {
success = true
})))
var input TestInput
require.Error(t, depinject.InjectDebug(
conditionalDebugOpt,
depinject.Configs(),
&input,
))
require.Contains(t, logs, `Initializing logger`)
require.Contains(t, logs, `Registering providers`)
require.Contains(t, logs, `Registering outputs`)
require.False(t, success)
logs = ""
success = false
require.NoError(t, depinject.InjectDebug(
conditionalDebugOpt,
depinject.Configs(),
))
require.Empty(t, logs)
require.True(t, success)
}