cosmos-sdk/container/container_test.go

545 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.RunDebug(
func() {},
container.DebugOptions(
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")
}