226 lines
5.3 KiB
Go
226 lines
5.3 KiB
Go
package appconfig_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"sort"
|
|
"testing"
|
|
|
|
"gotest.tools/v3/assert"
|
|
|
|
"cosmossdk.io/depinject"
|
|
|
|
"cosmossdk.io/core/appconfig"
|
|
"cosmossdk.io/core/appmodule"
|
|
"cosmossdk.io/core/internal"
|
|
"cosmossdk.io/core/internal/testpb"
|
|
_ "cosmossdk.io/core/internal/testpb"
|
|
)
|
|
|
|
func expectContainerErrorContains(t *testing.T, option depinject.Config, contains string) {
|
|
t.Helper()
|
|
err := depinject.Inject(option)
|
|
assert.ErrorContains(t, err, contains)
|
|
}
|
|
|
|
func TestCompose(t *testing.T) {
|
|
opt := appconfig.LoadJSON([]byte(`{"modules":[{}]}`))
|
|
expectContainerErrorContains(t, opt, "module is missing name")
|
|
|
|
opt = appconfig.LoadJSON([]byte(`{"modules":[{"name": "a"}]}`))
|
|
expectContainerErrorContains(t, opt, `module "a" is missing a config object`)
|
|
|
|
opt = appconfig.LoadYAML([]byte(`
|
|
modules:
|
|
- name: a
|
|
config:
|
|
"@type": testpb.ModuleFoo
|
|
`))
|
|
expectContainerErrorContains(t, opt, `unable to resolve`)
|
|
|
|
opt = appconfig.LoadYAML([]byte(`
|
|
modules:
|
|
- name: a
|
|
config:
|
|
"@type": cosmos.app.v1alpha1.Config # this is not actually a module config type!
|
|
`))
|
|
expectContainerErrorContains(t, opt, "does not have the option cosmos.app.v1alpha1.module")
|
|
expectContainerErrorContains(t, opt, "registered modules are")
|
|
expectContainerErrorContains(t, opt, "testpb.TestModuleA")
|
|
|
|
opt = appconfig.LoadYAML([]byte(`
|
|
modules:
|
|
- name: a
|
|
config:
|
|
"@type": testpb.TestUnregisteredModule
|
|
`))
|
|
expectContainerErrorContains(t, opt, "did you forget to import cosmossdk.io/core/internal/testpb")
|
|
expectContainerErrorContains(t, opt, "registered modules are")
|
|
expectContainerErrorContains(t, opt, "testpb.TestModuleA")
|
|
|
|
var app App
|
|
opt = appconfig.LoadYAML([]byte(`
|
|
modules:
|
|
- name: runtime
|
|
config:
|
|
"@type": testpb.TestRuntimeModule
|
|
- name: a
|
|
config:
|
|
"@type": testpb.TestModuleA
|
|
- name: b
|
|
config:
|
|
"@type": testpb.TestModuleB
|
|
`))
|
|
assert.NilError(t, depinject.Inject(opt, &app))
|
|
buf := &bytes.Buffer{}
|
|
app(buf)
|
|
const expected = `got store key a
|
|
got store key b
|
|
running module handler a
|
|
result: hello
|
|
running module handler b
|
|
result: goodbye
|
|
`
|
|
assert.Equal(t, expected, buf.String())
|
|
|
|
opt = appconfig.LoadYAML([]byte(`
|
|
golang_bindings:
|
|
- interfaceType: interfaceType/package.name
|
|
implementation: implementationType/package.name
|
|
- interfaceType: interfaceType/package.nameTwo
|
|
implementation: implementationType/package.nameTwo
|
|
modules:
|
|
- name: a
|
|
config:
|
|
"@type": testpb.TestModuleA
|
|
golang_bindings:
|
|
- interfaceType: interfaceType/package.name
|
|
implementation: implementationType/package.name
|
|
- interfaceType: interfaceType/package.nameTwo
|
|
implementation: implementationType/package.nameTwo
|
|
`))
|
|
assert.NilError(t, depinject.Inject(opt))
|
|
|
|
// module registration failures:
|
|
appmodule.Register(&testpb.TestNoModuleOptionModule{})
|
|
opt = appconfig.LoadYAML([]byte(`
|
|
modules:
|
|
- name: a
|
|
config:
|
|
"@type": testpb.TestNoGoImportModule
|
|
`))
|
|
expectContainerErrorContains(t, opt, "module should have the option cosmos.app.v1alpha1.module")
|
|
|
|
internal.ModuleRegistry = map[reflect.Type]*internal.ModuleInitializer{} // reset module registry
|
|
appmodule.Register(&testpb.TestNoGoImportModule{})
|
|
opt = appconfig.LoadYAML([]byte(`
|
|
modules:
|
|
- name: a
|
|
config:
|
|
"@type": testpb.TestNoGoImportModule
|
|
`))
|
|
expectContainerErrorContains(t, opt, "module should have ModuleDescriptor.go_import specified")
|
|
}
|
|
|
|
//
|
|
// Test Module Initialization Logic
|
|
//
|
|
|
|
func init() {
|
|
appmodule.Register(&testpb.TestRuntimeModule{},
|
|
appmodule.Provide(ProvideRuntimeState, ProvideStoreKey, ProvideApp),
|
|
)
|
|
|
|
appmodule.Register(&testpb.TestModuleA{},
|
|
appmodule.Provide(ProvideModuleA),
|
|
)
|
|
|
|
appmodule.Register(&testpb.TestModuleB{},
|
|
appmodule.Provide(ProvideModuleB),
|
|
)
|
|
}
|
|
|
|
func ProvideRuntimeState() *RuntimeState {
|
|
return &RuntimeState{}
|
|
}
|
|
|
|
func ProvideStoreKey(key depinject.ModuleKey, state *RuntimeState) StoreKey {
|
|
sk := StoreKey{name: key.Name()}
|
|
state.storeKeys = append(state.storeKeys, sk)
|
|
return sk
|
|
}
|
|
|
|
func ProvideApp(state *RuntimeState, handlers map[string]Handler) App {
|
|
return func(w io.Writer) {
|
|
sort.Slice(state.storeKeys, func(i, j int) bool {
|
|
return state.storeKeys[i].name < state.storeKeys[j].name
|
|
})
|
|
|
|
for _, key := range state.storeKeys {
|
|
_, _ = fmt.Fprintf(w, "got store key %s\n", key.name)
|
|
}
|
|
|
|
var modNames []string
|
|
for modName := range handlers {
|
|
modNames = append(modNames, modName)
|
|
}
|
|
|
|
sort.Strings(modNames)
|
|
for _, name := range modNames {
|
|
_, _ = fmt.Fprintf(w, "running module handler %s\n", name)
|
|
_, _ = fmt.Fprintf(w, "result: %s\n", handlers[name].DoSomething())
|
|
}
|
|
}
|
|
}
|
|
|
|
type App func(writer io.Writer)
|
|
|
|
type RuntimeState struct {
|
|
storeKeys []StoreKey
|
|
}
|
|
|
|
type StoreKey struct{ name string }
|
|
|
|
type Handler struct {
|
|
DoSomething func() string
|
|
}
|
|
|
|
func (h Handler) IsOnePerModuleType() {}
|
|
|
|
func ProvideModuleA(key StoreKey) (KeeperA, Handler) {
|
|
return keeperA{key: key}, Handler{DoSomething: func() string {
|
|
return "hello"
|
|
}}
|
|
}
|
|
|
|
type keeperA struct {
|
|
key StoreKey
|
|
}
|
|
|
|
type KeeperA interface {
|
|
Foo()
|
|
}
|
|
|
|
func (k keeperA) Foo() {}
|
|
|
|
func ProvideModuleB(key StoreKey, a KeeperA) (KeeperB, Handler) {
|
|
return keeperB{key: key, a: a}, Handler{
|
|
DoSomething: func() string {
|
|
return "goodbye"
|
|
},
|
|
}
|
|
}
|
|
|
|
type keeperB struct {
|
|
key StoreKey
|
|
a KeeperA
|
|
}
|
|
|
|
type KeeperB interface {
|
|
isKeeperB()
|
|
}
|
|
|
|
func (k keeperB) isKeeperB() {}
|