Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mint: change tests #3662

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Misc Improvements

* [#3611](https://github.com/osmosis-labs/osmosis/pull/3611) Introduce osmocli, to automate thousands of lines of CLI boilerplate
* [#3611](https://github.com/osmosis-labs/osmosis/pull/3611),[#3647](https://github.com/osmosis-labs/osmosis/pull/3647) Introduce osmocli, to automate thousands of lines of CLI boilerplate
* [#3634](https://github.com/osmosis-labs/osmosis/pull/3634) (Makefile) Ensure correct golang version in make build and make install. (Thank you @jhernandezb )

## v13.0.0
Expand Down
12 changes: 12 additions & 0 deletions osmoutils/cli_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/testutil/network"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/crypto/ed25519"
)

func DefaultFeeString(cfg network.Config) string {
Expand Down Expand Up @@ -72,3 +73,14 @@ func ParseSdkValAddressFromString(s string, separator string) []sdk.ValAddress {

return parsedAddr
}

// CreateRandomAccounts is a function return a list of randomly generated AccAddresses
func CreateRandomAccounts(numAccts int) []sdk.AccAddress {
testAddrs := make([]sdk.AccAddress, numAccts)
for i := 0; i < numAccts; i++ {
pk := ed25519.GenPrivKey().PubKey()
testAddrs[i] = sdk.AccAddress(pk.Address())
}

return testAddrs
}
111 changes: 111 additions & 0 deletions osmoutils/osmocli/cli_tester.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package osmocli

import (
"strings"
"testing"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/gogo/protobuf/proto"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/stretchr/testify/require"
)

type TxCliTestCase[M sdk.Msg] struct {
Cmd string
ExpectedMsg M
ExpectedErr bool
OnlyCheckValidateBasic bool
}

type QueryCliTestCase[Q proto.Message] struct {
Cmd string
ExpectedQuery Q
ExpectedErr bool
}

func RunTxTestCases[M sdk.Msg](t *testing.T, desc *TxCliDesc, testcases map[string]TxCliTestCase[M]) {
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
RunTxTestCase(t, desc, &tc)
})
}
}

func RunQueryTestCases[Q proto.Message](t *testing.T, desc *QueryDescriptor, testcases map[string]QueryCliTestCase[Q]) {
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
RunQueryTestCase(t, desc, &tc)
})
}
}

func RunTxTestCase[M sdk.Msg](t *testing.T, desc *TxCliDesc, tc *TxCliTestCase[M]) {
cmd := BuildTxCli[M](desc)
err := resetCommandFlagValues(cmd)
require.NoError(t, err, "error in resetCommandFlagValues")
args := strings.Split(tc.Cmd, " ")
err = cmd.Flags().Parse(args)
require.NoError(t, err, "error in cmd.Flags().Parse(args)")
clientCtx := newClientContextWithFrom(t, cmd.Flags())

msg, err := desc.ParseAndBuildMsg(clientCtx, args, cmd.Flags())
if tc.ExpectedErr {
require.Error(t, err)
return
}
require.NoError(t, err, "error in desc.ParseAndBuildMsg")
if tc.OnlyCheckValidateBasic {
require.NoError(t, msg.ValidateBasic())
return
}

require.Equal(t, tc.ExpectedMsg, msg)
}

func RunQueryTestCase[Q proto.Message](t *testing.T, desc *QueryDescriptor, tc *QueryCliTestCase[Q]) {
cmd := BuildQueryCli[Q, int](desc, nil)
err := resetCommandFlagValues(cmd)
require.NoError(t, err, "error in resetCommandFlagValues")
args := strings.Split(tc.Cmd, " ")
err = cmd.Flags().Parse(args)
require.NoError(t, err, "error in cmd.Flags().Parse(args)")

req, err := desc.ParseQuery(args, cmd.Flags())
if tc.ExpectedErr {
require.Error(t, err)
return
}
require.NoError(t, err, "error in desc.ParseQuery")
require.Equal(t, tc.ExpectedQuery, req)
}

// This logic is copied from the SDK, it should've just been publicly exposed.
// But instead its buried within a mega-method.
func newClientContextWithFrom(t *testing.T, fs *pflag.FlagSet) client.Context {
clientCtx := client.Context{}
from, _ := fs.GetString(flags.FlagFrom)
fromAddr, fromName, _, err := client.GetFromFields(nil, from, true)
require.NoError(t, err)

clientCtx = clientCtx.WithFrom(from).WithFromAddress(fromAddr).WithFromName(fromName)
return clientCtx
}

// taken from https://github.com/golang/debug/pull/8,
// due to no cobra command for resetting flag value
func resetCommandFlagValues(cmd *cobra.Command) error {
var retErr error = nil
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if f.Changed {
err := f.Value.Set(f.DefValue)
if err != nil {
retErr = err
}
f.Changed = false
}
})
return retErr
}
97 changes: 69 additions & 28 deletions osmoutils/osmocli/query_cmd_wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@ import (
grpc1 "github.com/gogo/protobuf/grpc"
"github.com/gogo/protobuf/proto"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

func QueryIndexCmd(moduleName string) *cobra.Command {
cmd := IndexCmd(moduleName)
cmd.Short = fmt.Sprintf("Querying commands for the %s module", moduleName)
return cmd
}
// global variable set on index command.
// helps populate Longs, when not set in QueryDescriptor.
var lastQueryModuleName string

type QueryDescriptor struct {
Use string
Expand All @@ -33,25 +32,70 @@ type QueryDescriptor struct {
CustomFlagOverrides map[string]string
// Map of FieldName -> CustomParseFn
CustomFieldParsers map[string]CustomFieldParserFn

ParseQuery func(args []string, flags *pflag.FlagSet) (proto.Message, error)

ModuleName string
numArgs int
}

func SimpleQueryFromDescriptor[reqP proto.Message, querier any](desc QueryDescriptor, newQueryClientFn func(grpc1.ClientConn) querier) *cobra.Command {
numArgs := ParseNumFields[reqP]() - len(desc.CustomFlagOverrides)
func QueryIndexCmd(moduleName string) *cobra.Command {
cmd := IndexCmd(moduleName)
cmd.Short = fmt.Sprintf("Querying commands for the %s module", moduleName)
lastQueryModuleName = moduleName
return cmd
}

func AddQueryCmd[Q proto.Message, querier any](cmd *cobra.Command, newQueryClientFn func(grpc1.ClientConn) querier, f func() (*QueryDescriptor, Q)) {
desc, _ := f()
prepareDescriptor[Q](desc)
subCmd := BuildQueryCli[Q](desc, newQueryClientFn)
cmd.AddCommand(subCmd)
}

func (desc *QueryDescriptor) FormatLong(moduleName string) {
desc.Long = FormatLongDesc(desc.Long, NewLongMetadata(moduleName).WithShort(desc.Short))
}

func prepareDescriptor[reqP proto.Message](desc *QueryDescriptor) {
if !desc.HasPagination {
desc.HasPagination = ParseHasPagination[reqP]()
}
if desc.QueryFnName == "" {
desc.QueryFnName = ParseExpectedQueryFnName[reqP]()
}
if strings.Contains(desc.Long, "{") {
if desc.ModuleName == "" {
desc.ModuleName = lastQueryModuleName
}
desc.FormatLong(desc.ModuleName)
}

desc.numArgs = ParseNumFields[reqP]() - len(desc.CustomFlagOverrides)
if desc.HasPagination {
numArgs = numArgs - 1
desc.numArgs = desc.numArgs - 1
}
}

func BuildQueryCli[reqP proto.Message, querier any](desc *QueryDescriptor, newQueryClientFn func(grpc1.ClientConn) querier) *cobra.Command {
prepareDescriptor[reqP](desc)
if desc.ParseQuery == nil {
desc.ParseQuery = func(args []string, fs *pflag.FlagSet) (proto.Message, error) {
flagAdvice := FlagAdvice{
HasPagination: desc.HasPagination,
CustomFlagOverrides: desc.CustomFlagOverrides,
CustomFieldParsers: desc.CustomFieldParsers,
}.Sanitize()
return ParseFieldsFromFlagsAndArgs[reqP](flagAdvice, fs, args)
}
}
flagAdvice := FlagAdvice{
HasPagination: desc.HasPagination,
CustomFlagOverrides: desc.CustomFlagOverrides,
CustomFieldParsers: desc.CustomFieldParsers,
}.Sanitize()

cmd := &cobra.Command{
Use: desc.Use,
Short: desc.Short,
Long: desc.Long,
Args: cobra.ExactArgs(numArgs),
RunE: NewQueryLogicAllFieldsAsArgs[reqP](
flagAdvice, desc.QueryFnName, newQueryClientFn),
Args: cobra.ExactArgs(desc.numArgs),
RunE: queryLogic(desc, newQueryClientFn),
}
flags.AddQueryFlagsToCmd(cmd)
AddFlags(cmd, desc.Flags)
Expand All @@ -70,25 +114,23 @@ func SimpleQueryFromDescriptor[reqP proto.Message, querier any](desc QueryDescri
func SimpleQueryCmd[reqP proto.Message, querier any](use string, short string, long string,
moduleName string, newQueryClientFn func(grpc1.ClientConn) querier) *cobra.Command {
desc := QueryDescriptor{
Use: use,
Short: short,
Long: FormatLongDesc(long, NewLongMetadata(moduleName).WithShort(short)),
HasPagination: ParseHasPagination[reqP](),
QueryFnName: ParseExpectedQueryFnName[reqP](),
Use: use,
Short: short,
Long: FormatLongDesc(long, NewLongMetadata(moduleName).WithShort(short)),
}
return SimpleQueryFromDescriptor[reqP](desc, newQueryClientFn)
return BuildQueryCli[reqP](&desc, newQueryClientFn)
}

func GetParams[reqP proto.Message, querier any](moduleName string,
newQueryClientFn func(grpc1.ClientConn) querier) *cobra.Command {
return SimpleQueryFromDescriptor[reqP](QueryDescriptor{
return BuildQueryCli[reqP](&QueryDescriptor{
Use: "params [flags]",
Short: fmt.Sprintf("Get the params for the x/%s module", moduleName),
QueryFnName: "Params",
}, newQueryClientFn)
}

func callQueryClientFn[reqP proto.Message, querier any](ctx context.Context, fnName string, req reqP, q querier) (res proto.Message, err error) {
func callQueryClientFn(ctx context.Context, fnName string, req proto.Message, q any) (res proto.Message, err error) {
qVal := reflect.ValueOf(q)
method := qVal.MethodByName(fnName)
args := []reflect.Value{
Expand All @@ -109,22 +151,21 @@ func callQueryClientFn[reqP proto.Message, querier any](ctx context.Context, fnN
return res, nil
}

func NewQueryLogicAllFieldsAsArgs[reqP proto.Message, querier any](flagAdvice FlagAdvice, keeperFnName string,
func queryLogic[querier any](desc *QueryDescriptor,
newQueryClientFn func(grpc1.ClientConn) querier) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := newQueryClientFn(clientCtx)
var req reqP

req, err = ParseFieldsFromFlagsAndArgs[reqP](flagAdvice, cmd.Flags(), args)
req, err := desc.ParseQuery(args, cmd.Flags())
if err != nil {
return err
}

res, err := callQueryClientFn(cmd.Context(), keeperFnName, req, queryClient)
res, err := callQueryClientFn(cmd.Context(), desc.QueryFnName, req, queryClient)
if err != nil {
return err
}
Expand Down
26 changes: 17 additions & 9 deletions osmoutils/osmocli/tx_cmd_wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,29 @@ type TxCliDesc struct {
CustomFieldParsers map[string]CustomFieldParserFn
}

func AddTxCmd[M sdk.Msg](cmd *cobra.Command, f func() (*TxCliDesc, M)) {
desc, _ := f()
subCmd := BuildTxCli[M](desc)
cmd.AddCommand(subCmd)
}

func BuildTxCli[M sdk.Msg](desc *TxCliDesc) *cobra.Command {
desc.TxSignerFieldName = strings.ToLower(desc.TxSignerFieldName)
if desc.NumArgs == 0 {
// NumArgs = NumFields - 1, since 1 field is from the msg
desc.NumArgs = ParseNumFields[M]() - 1 - len(desc.CustomFlagOverrides) - len(desc.CustomFieldParsers)
}
desc.ParseAndBuildMsg = func(clientCtx client.Context, args []string, flags *pflag.FlagSet) (sdk.Msg, error) {
flagAdvice := FlagAdvice{
IsTx: true,
TxSenderFieldName: desc.TxSignerFieldName,
FromValue: clientCtx.GetFromAddress().String(),
CustomFlagOverrides: desc.CustomFlagOverrides,
CustomFieldParsers: desc.CustomFieldParsers,
}.Sanitize()
return ParseFieldsFromFlagsAndArgs[M](flagAdvice, flags, args)
if desc.ParseAndBuildMsg == nil {
desc.ParseAndBuildMsg = func(clientCtx client.Context, args []string, flags *pflag.FlagSet) (sdk.Msg, error) {
flagAdvice := FlagAdvice{
IsTx: true,
TxSenderFieldName: desc.TxSignerFieldName,
FromValue: clientCtx.GetFromAddress().String(),
CustomFlagOverrides: desc.CustomFlagOverrides,
CustomFieldParsers: desc.CustomFieldParsers,
}.Sanitize()
return ParseFieldsFromFlagsAndArgs[M](flagAdvice, flags, args)
}
}
return desc.BuildCommandCustomFn()
}
Expand Down
Loading