543 lines
11 KiB
Go
543 lines
11 KiB
Go
package container_test
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/cosmos/cosmos-sdk/container"
|
|
)
|
|
|
|
type KVStoreKey struct {
|
|
name string
|
|
}
|
|
|
|
type ModuleKey string
|
|
|
|
type MsgClientA struct {
|
|
key ModuleKey
|
|
}
|
|
|
|
type KeeperA struct {
|
|
key KVStoreKey
|
|
}
|
|
|
|
type KeeperB struct {
|
|
key KVStoreKey
|
|
msgClientA MsgClientA
|
|
}
|
|
|
|
type Handler struct {
|
|
Handle func()
|
|
}
|
|
|
|
func (Handler) IsOnePerScopeType() {}
|
|
|
|
type Command struct {
|
|
Run func()
|
|
}
|
|
|
|
func (Command) IsAutoGroupType() {}
|
|
|
|
func ProvideKVStoreKey(scope container.Scope) KVStoreKey {
|
|
return KVStoreKey{name: scope.Name()}
|
|
}
|
|
|
|
func ProvideModuleKey(scope container.Scope) (ModuleKey, error) {
|
|
return ModuleKey(scope.Name()), nil
|
|
}
|
|
|
|
func ProvideMsgClientA(_ container.Scope, key ModuleKey) MsgClientA {
|
|
return MsgClientA{key}
|
|
}
|
|
|
|
type ModuleA struct{}
|
|
|
|
func (ModuleA) Provide(key KVStoreKey) (KeeperA, Handler, Command) {
|
|
return KeeperA{key}, Handler{}, Command{}
|
|
}
|
|
|
|
type ModuleB struct{}
|
|
|
|
type BDependencies struct {
|
|
container.In
|
|
|
|
Key KVStoreKey
|
|
A MsgClientA
|
|
}
|
|
|
|
type BProvides struct {
|
|
container.Out
|
|
|
|
KeeperB KeeperB
|
|
Commands []Command
|
|
}
|
|
|
|
func (ModuleB) Provide(dependencies BDependencies, _ container.Scope) (BProvides, Handler, error) {
|
|
return BProvides{
|
|
KeeperB: KeeperB{
|
|
key: dependencies.Key,
|
|
msgClientA: dependencies.A,
|
|
},
|
|
Commands: []Command{{}, {}},
|
|
}, Handler{}, nil
|
|
}
|
|
|
|
func TestScenario(t *testing.T) {
|
|
require.NoError(t,
|
|
container.Run(
|
|
func(handlers map[string]Handler, commands []Command, a KeeperA, b KeeperB) {
|
|
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"},
|
|
}, a)
|
|
require.Equal(t, KeeperB{
|
|
key: KVStoreKey{name: "b"},
|
|
msgClientA: MsgClientA{
|
|
key: "b",
|
|
},
|
|
}, b)
|
|
},
|
|
container.Provide(
|
|
ProvideKVStoreKey,
|
|
ProvideModuleKey,
|
|
ProvideMsgClientA,
|
|
),
|
|
container.ProvideWithScope("a", wrapMethod0(ModuleA{})),
|
|
container.ProvideWithScope("b", wrapMethod0(ModuleB{})),
|
|
))
|
|
}
|
|
|
|
func wrapMethod0(module interface{}) interface{} {
|
|
methodFn := reflect.TypeOf(module).Method(0).Func.Interface()
|
|
ctrInfo, err := container.ExtractProviderDescriptor(methodFn)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
ctrInfo.Inputs = ctrInfo.Inputs[1:]
|
|
fn := ctrInfo.Fn
|
|
ctrInfo.Fn = func(values []reflect.Value) ([]reflect.Value, error) {
|
|
return fn(append([]reflect.Value{reflect.ValueOf(module)}, values...))
|
|
}
|
|
return ctrInfo
|
|
}
|
|
|
|
func TestResolveError(t *testing.T) {
|
|
require.Error(t, container.Run(
|
|
func(x string) {},
|
|
container.Provide(
|
|
func(x float64) string { return fmt.Sprintf("%f", x) },
|
|
func(x int) float64 { return float64(x) },
|
|
func(x float32) int { return int(x) },
|
|
),
|
|
))
|
|
}
|
|
|
|
func TestCyclic(t *testing.T) {
|
|
require.Error(t, container.Run(
|
|
func(x string) {},
|
|
container.Provide(
|
|
func(x int) float64 { return float64(x) },
|
|
func(x float64) (int, string) { return int(x), "hi" },
|
|
),
|
|
))
|
|
}
|
|
|
|
func TestErrorOption(t *testing.T) {
|
|
err := container.Run(func() {}, container.Error(fmt.Errorf("an error")))
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestBadCtr(t *testing.T) {
|
|
_, err := container.ExtractProviderDescriptor(KeeperA{})
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestInvoker(t *testing.T) {
|
|
require.NoError(t, container.Run(func() {}))
|
|
require.NoError(t, container.Run(func() error { return nil }))
|
|
require.Error(t, container.Run(func() error { return fmt.Errorf("error") }))
|
|
require.Error(t, container.Run(func() int { return 0 }))
|
|
}
|
|
|
|
func TestErrorFunc(t *testing.T) {
|
|
_, err := container.ExtractProviderDescriptor(
|
|
func() (error, int) { return nil, 0 },
|
|
)
|
|
require.Error(t, err)
|
|
|
|
_, err = container.ExtractProviderDescriptor(
|
|
func() (int, error) { return 0, nil },
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
require.Error(t,
|
|
container.Run(
|
|
func(x int) {
|
|
},
|
|
container.Provide(func() (int, error) {
|
|
return 0, fmt.Errorf("the error")
|
|
}),
|
|
))
|
|
|
|
require.Error(t,
|
|
container.Run(func() error {
|
|
return fmt.Errorf("the error")
|
|
}), "the error")
|
|
}
|
|
|
|
func TestSimple(t *testing.T) {
|
|
require.NoError(t,
|
|
container.Run(
|
|
func(x int) {
|
|
require.Equal(t, 1, x)
|
|
},
|
|
container.Provide(
|
|
func() int { return 1 },
|
|
),
|
|
),
|
|
)
|
|
|
|
require.Error(t,
|
|
container.Run(func(int) {},
|
|
container.Provide(
|
|
func() int { return 0 },
|
|
func() int { return 1 },
|
|
),
|
|
),
|
|
)
|
|
}
|
|
|
|
func TestScoped(t *testing.T) {
|
|
require.Error(t,
|
|
container.Run(func(int) {},
|
|
container.Provide(
|
|
func(container.Scope) int { return 0 },
|
|
),
|
|
),
|
|
)
|
|
|
|
require.Error(t,
|
|
container.Run(func(float64) {},
|
|
container.Provide(
|
|
func(container.Scope) int { return 0 },
|
|
func() int { return 1 },
|
|
),
|
|
container.ProvideWithScope("a",
|
|
func(x int) float64 { return float64(x) },
|
|
),
|
|
),
|
|
)
|
|
|
|
require.Error(t,
|
|
container.Run(func(float64) {},
|
|
container.Provide(
|
|
func() int { return 0 },
|
|
func(container.Scope) int { return 1 },
|
|
),
|
|
container.ProvideWithScope("a",
|
|
func(x int) float64 { return float64(x) },
|
|
),
|
|
),
|
|
)
|
|
|
|
require.Error(t,
|
|
container.Run(func(float64) {},
|
|
container.Provide(
|
|
func(container.Scope) int { return 0 },
|
|
func(container.Scope) int { return 1 },
|
|
),
|
|
container.ProvideWithScope("a",
|
|
func(x int) float64 { return float64(x) },
|
|
),
|
|
),
|
|
)
|
|
|
|
require.NoError(t,
|
|
container.Run(func(float64) {},
|
|
container.Provide(
|
|
func(container.Scope) int { return 0 },
|
|
),
|
|
container.ProvideWithScope("a",
|
|
func(x int) float64 { return float64(x) },
|
|
),
|
|
),
|
|
)
|
|
|
|
require.Error(t,
|
|
container.Run(func(float64) {},
|
|
container.Provide(
|
|
func(container.Scope) int { return 0 },
|
|
),
|
|
container.ProvideWithScope("",
|
|
func(x int) float64 { return float64(x) },
|
|
),
|
|
),
|
|
)
|
|
|
|
require.NoError(t,
|
|
container.Run(func(float64, float32) {},
|
|
container.Provide(
|
|
func(container.Scope) int { return 0 },
|
|
),
|
|
container.ProvideWithScope("a",
|
|
func(x int) float64 { return float64(x) },
|
|
func(x int) float32 { return float32(x) },
|
|
),
|
|
),
|
|
"use scope dep twice",
|
|
)
|
|
}
|
|
|
|
type OnePerScopeInt int
|
|
|
|
func (OnePerScopeInt) IsOnePerScopeType() {}
|
|
|
|
func TestOnePerScope(t *testing.T) {
|
|
require.Error(t,
|
|
container.Run(
|
|
func(OnePerScopeInt) {},
|
|
),
|
|
"bad input type",
|
|
)
|
|
|
|
require.NoError(t,
|
|
container.Run(
|
|
func(x map[string]OnePerScopeInt, y string) {
|
|
require.Equal(t, map[string]OnePerScopeInt{
|
|
"a": 3,
|
|
"b": 4,
|
|
}, x)
|
|
require.Equal(t, "7", y)
|
|
},
|
|
container.ProvideWithScope("a",
|
|
func() OnePerScopeInt { return 3 },
|
|
),
|
|
container.ProvideWithScope("b",
|
|
func() OnePerScopeInt { return 4 },
|
|
),
|
|
container.Provide(func(x map[string]OnePerScopeInt) string {
|
|
sum := 0
|
|
for _, v := range x {
|
|
sum += int(v)
|
|
}
|
|
return fmt.Sprintf("%d", sum)
|
|
}),
|
|
),
|
|
)
|
|
|
|
require.Error(t,
|
|
container.Run(
|
|
func(map[string]OnePerScopeInt) {},
|
|
container.ProvideWithScope("a",
|
|
func() OnePerScopeInt { return 0 },
|
|
func() OnePerScopeInt { return 0 },
|
|
),
|
|
),
|
|
"duplicate",
|
|
)
|
|
|
|
require.Error(t,
|
|
container.Run(
|
|
func(map[string]OnePerScopeInt) {},
|
|
container.Provide(
|
|
func() OnePerScopeInt { return 0 },
|
|
),
|
|
),
|
|
"out of scope",
|
|
)
|
|
|
|
require.Error(t,
|
|
container.Run(
|
|
func(map[string]OnePerScopeInt) {},
|
|
container.Provide(
|
|
func() map[string]OnePerScopeInt { return nil },
|
|
),
|
|
),
|
|
"bad return type",
|
|
)
|
|
|
|
require.NoError(t,
|
|
container.Run(
|
|
func(map[string]OnePerScopeInt) {},
|
|
),
|
|
"no providers",
|
|
)
|
|
}
|
|
|
|
type AutoGroupInt int
|
|
|
|
func (AutoGroupInt) IsAutoGroupType() {}
|
|
|
|
func TestAutoGroup(t *testing.T) {
|
|
require.NoError(t,
|
|
container.Run(
|
|
func(xs []AutoGroupInt, sum string) {
|
|
require.Len(t, xs, 2)
|
|
require.Contains(t, xs, AutoGroupInt(4))
|
|
require.Contains(t, xs, AutoGroupInt(9))
|
|
require.Equal(t, "13", sum)
|
|
},
|
|
container.Provide(
|
|
func() AutoGroupInt { return 4 },
|
|
func() AutoGroupInt { return 9 },
|
|
func(xs []AutoGroupInt) string {
|
|
sum := 0
|
|
for _, x := range xs {
|
|
sum += int(x)
|
|
}
|
|
return fmt.Sprintf("%d", sum)
|
|
},
|
|
),
|
|
),
|
|
)
|
|
|
|
require.Error(t,
|
|
container.Run(
|
|
func(AutoGroupInt) {},
|
|
container.Provide(
|
|
func() AutoGroupInt { return 0 },
|
|
),
|
|
),
|
|
"bad input type",
|
|
)
|
|
|
|
require.NoError(t,
|
|
container.Run(
|
|
func([]AutoGroupInt) {},
|
|
),
|
|
"no providers",
|
|
)
|
|
}
|
|
|
|
func TestSupply(t *testing.T) {
|
|
require.NoError(t,
|
|
container.Run(func(x int) {
|
|
require.Equal(t, 3, x)
|
|
},
|
|
container.Supply(3),
|
|
),
|
|
)
|
|
|
|
require.Error(t,
|
|
container.Run(func(x int) {},
|
|
container.Supply(3),
|
|
container.Provide(func() int { return 4 }),
|
|
),
|
|
"can't supply then provide",
|
|
)
|
|
|
|
require.Error(t,
|
|
container.Run(func(x int) {},
|
|
container.Supply(3),
|
|
container.Provide(func() int { return 4 }),
|
|
),
|
|
"can't provide then supply",
|
|
)
|
|
|
|
require.Error(t,
|
|
container.Run(func(x int) {},
|
|
container.Supply(3, 4),
|
|
),
|
|
"can't supply twice",
|
|
)
|
|
}
|
|
|
|
type TestInput struct {
|
|
container.In
|
|
|
|
X int `optional:"true"`
|
|
Y float64
|
|
}
|
|
|
|
type TestOutput struct {
|
|
container.Out
|
|
|
|
X string
|
|
}
|
|
|
|
func TestStructArgs(t *testing.T) {
|
|
require.Error(t, container.Run(
|
|
func(input TestInput) {},
|
|
))
|
|
|
|
require.NoError(t, container.Run(
|
|
func(input TestInput) {
|
|
require.Equal(t, 0, input.X)
|
|
require.Equal(t, 1.3, input.Y)
|
|
},
|
|
container.Supply(1.3),
|
|
))
|
|
|
|
require.NoError(t, container.Run(
|
|
func(input TestInput) {
|
|
require.Equal(t, 1, input.X)
|
|
require.Equal(t, 1.3, input.Y)
|
|
},
|
|
container.Supply(1.3, 1),
|
|
))
|
|
|
|
require.NoError(t, container.Run(
|
|
func(x string) {
|
|
require.Equal(t, "A", x)
|
|
},
|
|
container.Provide(func() (TestOutput, error) {
|
|
return TestOutput{X: "A"}, nil
|
|
}),
|
|
))
|
|
|
|
require.Error(t, container.Run(
|
|
func(x string) {},
|
|
container.Provide(func() (TestOutput, error) {
|
|
return TestOutput{}, fmt.Errorf("error")
|
|
}),
|
|
))
|
|
}
|
|
|
|
func TestLogging(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 os.Remove(outfile.Name())
|
|
|
|
graphfile, err := os.CreateTemp("", "graph")
|
|
require.NoError(t, err)
|
|
defer os.Remove(graphfile.Name())
|
|
|
|
require.NoError(t, container.Run(
|
|
func() {},
|
|
container.Logger(func(s string) {
|
|
logOut += s
|
|
}),
|
|
container.Visualizer(func(g string) {
|
|
dotGraph = g
|
|
}),
|
|
container.LogVisualizer(),
|
|
container.FileVisualizer(graphfile.Name(), "svg"),
|
|
container.StdoutLogger(),
|
|
))
|
|
|
|
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), "<svg")
|
|
}
|