cosmos-sdk/server/util_test.go
Emmanuel T Odeke abe3e0c6da
perf: all: remove unnecessary allocations from strings.Builder.WriteString(fmt.Sprintf(...)) (#13230)
This change removes a code pattern that I noticed while on a late night
audit of cosmovisor in which
strings.Builder.WriteString(fmt.Sprintf(...))
calls were being made, yet that's counterproductive to using fmt.Fprintf
which will check whether the writer implements .WriteString and then
avoids the need to firstly build a string using fmt.Sprintf.

The performance wins from this change transcend all dimensions as
exhibited below:

```shell
$ benchstat before.txt after.txt
name            old time/op    new time/op    delta
DetailString-8    5.48µs ±23%    4.40µs ±11%  -19.79%  (p=0.000 n=20+17)

name            old alloc/op   new alloc/op   delta
DetailString-8    2.63kB ± 0%    2.11kB ± 0%  -19.76%  (p=0.000 n=20+20)

name            old allocs/op  new allocs/op  delta
DetailString-8      63.0 ± 0%      50.0 ± 0%  -20.63%  (p=0.000 n=20+20)
```

Fixes #13229
2022-09-09 21:57:46 +00:00

451 lines
14 KiB
Go

package server_test
import (
"context"
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
tmcfg "github.com/tendermint/tendermint/config"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/config"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/types/module/testutil"
genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
)
var cancelledInPreRun = errors.New("Cancelled in prerun")
// Used in each test to run the function under test via Cobra
// but to always halt the command
func preRunETestImpl(cmd *cobra.Command, args []string) error {
err := server.InterceptConfigsPreRunHandler(cmd, "", nil, tmcfg.DefaultConfig())
if err != nil {
return err
}
return cancelledInPreRun
}
func TestInterceptConfigsPreRunHandlerCreatesConfigFilesWhenMissing(t *testing.T) {
tempDir := t.TempDir()
cmd := server.StartCmd(nil, "/foobar")
if err := cmd.Flags().Set(flags.FlagHome, tempDir); err != nil {
t.Fatalf("Could not set home flag [%T] %v", err, err)
}
cmd.PreRunE = preRunETestImpl
serverCtx := &server.Context{}
ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx)
if err := cmd.ExecuteContext(ctx); err != cancelledInPreRun {
t.Fatalf("function failed with [%T] %v", err, err)
}
// Test that config.toml is created
configTomlPath := path.Join(tempDir, "config", "config.toml")
s, err := os.Stat(configTomlPath)
if err != nil {
t.Fatalf("Could not stat config.toml after run %v", err)
}
if !s.Mode().IsRegular() {
t.Fatal("config.toml not created as regular file")
}
if s.Size() == 0 {
t.Fatal("config.toml created as empty file")
}
// Test that tendermint config is initialized
if serverCtx.Config == nil {
t.Fatal("tendermint config not created")
}
// Test that app.toml is created
appTomlPath := path.Join(tempDir, "config", "app.toml")
s, err = os.Stat(appTomlPath)
if err != nil {
t.Fatalf("Could not stat app.toml after run %v", err)
}
if !s.Mode().IsRegular() {
t.Fatal("appp.toml not created as regular file")
}
if s.Size() == 0 {
t.Fatal("config.toml created as empty file")
}
// Test that the config for use in server/start.go is created
if serverCtx.Viper == nil {
t.Error("app config Viper instance not created")
}
}
func TestInterceptConfigsPreRunHandlerReadsConfigToml(t *testing.T) {
const testDbBackend = "awesome_test_db"
tempDir := t.TempDir()
err := os.Mkdir(path.Join(tempDir, "config"), os.ModePerm)
if err != nil {
t.Fatalf("creating config dir failed: %v", err)
}
configTomlPath := path.Join(tempDir, "config", "config.toml")
writer, err := os.Create(configTomlPath)
if err != nil {
t.Fatalf("creating config.toml file failed: %v", err)
}
_, err = fmt.Fprintf(writer, "db_backend = '%s'\n", testDbBackend)
if err != nil {
t.Fatalf("Failed writing string to config.toml: %v", err)
}
if err := writer.Close(); err != nil {
t.Fatalf("Failed closing config.toml: %v", err)
}
cmd := server.StartCmd(nil, "/foobar")
if err := cmd.Flags().Set(flags.FlagHome, tempDir); err != nil {
t.Fatalf("Could not set home flag [%T] %v", err, err)
}
cmd.PreRunE = preRunETestImpl
serverCtx := &server.Context{}
ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx)
if err := cmd.ExecuteContext(ctx); err != cancelledInPreRun {
t.Fatalf("function failed with [%T] %v", err, err)
}
if testDbBackend != serverCtx.Config.DBBackend {
t.Error("backend was not set from config.toml")
}
}
func TestInterceptConfigsPreRunHandlerReadsAppToml(t *testing.T) {
const testHaltTime = 1337
tempDir := t.TempDir()
err := os.Mkdir(path.Join(tempDir, "config"), os.ModePerm)
if err != nil {
t.Fatalf("creating config dir failed: %v", err)
}
appTomlPath := path.Join(tempDir, "config", "app.toml")
writer, err := os.Create(appTomlPath)
if err != nil {
t.Fatalf("creating app.toml file failed: %v", err)
}
_, err = fmt.Fprintf(writer, "halt-time = %d\n", testHaltTime)
if err != nil {
t.Fatalf("Failed writing string to app.toml: %v", err)
}
if err := writer.Close(); err != nil {
t.Fatalf("Failed closing app.toml: %v", err)
}
cmd := server.StartCmd(nil, tempDir)
cmd.PreRunE = preRunETestImpl
serverCtx := &server.Context{}
ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx)
if err := cmd.ExecuteContext(ctx); err != cancelledInPreRun {
t.Fatalf("function failed with [%T] %v", err, err)
}
if testHaltTime != serverCtx.Viper.GetInt("halt-time") {
t.Error("Halt time was not set from app.toml")
}
}
func TestInterceptConfigsPreRunHandlerReadsFlags(t *testing.T) {
const testAddr = "tcp://127.1.2.3:12345"
tempDir := t.TempDir()
cmd := server.StartCmd(nil, "/foobar")
if err := cmd.Flags().Set(flags.FlagHome, tempDir); err != nil {
t.Fatalf("Could not set home flag [%T] %v", err, err)
}
// This flag is added by tendermint
if err := cmd.Flags().Set("rpc.laddr", testAddr); err != nil {
t.Fatalf("Could not set address flag [%T] %v", err, err)
}
cmd.PreRunE = preRunETestImpl
serverCtx := &server.Context{}
ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx)
if err := cmd.ExecuteContext(ctx); err != cancelledInPreRun {
t.Fatalf("function failed with [%T] %v", err, err)
}
if testAddr != serverCtx.Config.RPC.ListenAddress {
t.Error("RPCListenAddress was not set from command flags")
}
}
func TestInterceptConfigsPreRunHandlerReadsEnvVars(t *testing.T) {
const testAddr = "tcp://127.1.2.3:12345"
tempDir := t.TempDir()
cmd := server.StartCmd(nil, "/foobar")
if err := cmd.Flags().Set(flags.FlagHome, tempDir); err != nil {
t.Fatalf("Could not set home flag [%T] %v", err, err)
}
executableName, err := os.Executable()
if err != nil {
t.Fatalf("Could not get executable name: %v", err)
}
basename := path.Base(executableName)
basename = strings.ReplaceAll(basename, ".", "_")
// This is added by tendermint
envVarName := fmt.Sprintf("%s_RPC_LADDR", strings.ToUpper(basename))
require.NoError(t, os.Setenv(envVarName, testAddr))
t.Cleanup(func() {
require.NoError(t, os.Unsetenv(envVarName))
})
cmd.PreRunE = preRunETestImpl
serverCtx := &server.Context{}
ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx)
if err := cmd.ExecuteContext(ctx); err != cancelledInPreRun {
t.Fatalf("function failed with [%T] %v", err, err)
}
if testAddr != serverCtx.Config.RPC.ListenAddress {
t.Errorf("RPCListenAddress was not set from env. var. %q", envVarName)
}
}
/*
The following tests are here to check the precedence of each
of the configuration sources. A common setup functionality is used
to avoid duplication of code between tests.
*/
var (
TestAddrExpected = "tcp://127.126.125.124:12345" // expected to be used in test
TestAddrNotExpected = "tcp://127.127.127.127:11111" // not expected to be used in test
)
type precedenceCommon struct {
envVarName string
flagName string
configTomlPath string
cmd *cobra.Command
}
func newPrecedenceCommon(t *testing.T) precedenceCommon {
retval := precedenceCommon{}
// Determine the env. var. name based off the executable name
executableName, err := os.Executable()
if err != nil {
t.Fatalf("Could not get executable name: %v", err)
}
basename := path.Base(executableName)
basename = strings.ReplaceAll(basename, ".", "_")
basename = strings.ReplaceAll(basename, "-", "_")
// Store the name of the env. var.
retval.envVarName = fmt.Sprintf("%s_RPC_LADDR", strings.ToUpper(basename))
// Store the flag name. This flag is added by tendermint
retval.flagName = "rpc.laddr"
// Create a tempdir and create './config' under that
tempDir := t.TempDir()
err = os.Mkdir(path.Join(tempDir, "config"), os.ModePerm)
if err != nil {
t.Fatalf("creating config dir failed: %v", err)
}
// Store the path for config.toml
retval.configTomlPath = path.Join(tempDir, "config", "config.toml")
// always remove the env. var. after each test execution
t.Cleanup(func() {
// This should not fail but if it does just panic
if err := os.Unsetenv(retval.envVarName); err != nil {
panic("Could not clear configuration env. var. used in test")
}
})
// Set up the command object that is used in this test
retval.cmd = server.StartCmd(nil, tempDir)
retval.cmd.PreRunE = preRunETestImpl
return retval
}
func (v precedenceCommon) setAll(t *testing.T, setFlag *string, setEnvVar *string, setConfigFile *string) {
if setFlag != nil {
if err := v.cmd.Flags().Set(v.flagName, *setFlag); err != nil {
t.Fatalf("Failed setting flag %q", v.flagName)
}
}
if setEnvVar != nil {
require.NoError(t, os.Setenv(v.envVarName, *setEnvVar))
}
if setConfigFile != nil {
writer, err := os.Create(v.configTomlPath)
if err != nil {
t.Fatalf("creating config.toml file failed: %v", err)
}
_, err = fmt.Fprintf(writer, "[rpc]\nladdr = \"%s\"\n", *setConfigFile)
if err != nil {
t.Fatalf("Failed writing string to config.toml: %v", err)
}
if err := writer.Close(); err != nil {
t.Fatalf("Failed closing config.toml: %v", err)
}
}
}
func TestInterceptConfigsPreRunHandlerPrecedenceFlag(t *testing.T) {
testCommon := newPrecedenceCommon(t)
testCommon.setAll(t, &TestAddrExpected, &TestAddrNotExpected, &TestAddrNotExpected)
serverCtx := &server.Context{}
ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx)
if err := testCommon.cmd.ExecuteContext(ctx); err != cancelledInPreRun {
t.Fatalf("function failed with [%T] %v", err, err)
}
if TestAddrExpected != serverCtx.Config.RPC.ListenAddress {
t.Fatalf("RPCListenAddress was not set from flag %q", testCommon.flagName)
}
}
func TestInterceptConfigsPreRunHandlerPrecedenceEnvVar(t *testing.T) {
testCommon := newPrecedenceCommon(t)
testCommon.setAll(t, nil, &TestAddrExpected, &TestAddrNotExpected)
serverCtx := &server.Context{}
ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx)
if err := testCommon.cmd.ExecuteContext(ctx); err != cancelledInPreRun {
t.Fatalf("function failed with [%T] %v", err, err)
}
if TestAddrExpected != serverCtx.Config.RPC.ListenAddress {
t.Errorf("RPCListenAddress was not set from env. var. %q", testCommon.envVarName)
}
}
func TestInterceptConfigsPreRunHandlerPrecedenceConfigFile(t *testing.T) {
testCommon := newPrecedenceCommon(t)
testCommon.setAll(t, nil, nil, &TestAddrExpected)
serverCtx := &server.Context{}
ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx)
if err := testCommon.cmd.ExecuteContext(ctx); err != cancelledInPreRun {
t.Fatalf("function failed with [%T] %v", err, err)
}
if TestAddrExpected != serverCtx.Config.RPC.ListenAddress {
t.Errorf("RPCListenAddress was not read from file %q", testCommon.configTomlPath)
}
}
func TestInterceptConfigsPreRunHandlerPrecedenceConfigDefault(t *testing.T) {
testCommon := newPrecedenceCommon(t)
// Do not set anything
serverCtx := &server.Context{}
ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx)
if err := testCommon.cmd.ExecuteContext(ctx); err != cancelledInPreRun {
t.Fatalf("function failed with [%T] %v", err, err)
}
if "tcp://127.0.0.1:26657" != serverCtx.Config.RPC.ListenAddress {
t.Error("RPCListenAddress is not using default")
}
}
// Ensure that if interceptConfigs encounters any error other than non-existen errors
// that we correctly return the offending error, for example a permission error.
// See https://github.com/cosmos/cosmos-sdk/issues/7578
func TestInterceptConfigsWithBadPermissions(t *testing.T) {
tempDir := t.TempDir()
subDir := filepath.Join(tempDir, "nonPerms")
if err := os.Mkdir(subDir, 0o600); err != nil {
t.Fatalf("Failed to create sub directory: %v", err)
}
cmd := server.StartCmd(nil, "/foobar")
if err := cmd.Flags().Set(flags.FlagHome, subDir); err != nil {
t.Fatalf("Could not set home flag [%T] %v", err, err)
}
cmd.PreRunE = preRunETestImpl
serverCtx := &server.Context{}
ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx)
if err := cmd.ExecuteContext(ctx); !os.IsPermission(err) {
t.Fatalf("Failed to catch permissions error, got: [%T] %v", err, err)
}
}
func TestEmptyMinGasPrices(t *testing.T) {
tempDir := t.TempDir()
err := os.Mkdir(filepath.Join(tempDir, "config"), os.ModePerm)
require.NoError(t, err)
encCfg := testutil.MakeTestEncodingConfig()
// Run InitCmd to create necessary config files.
clientCtx := client.Context{}.WithHomeDir(tempDir).WithCodec(encCfg.Codec)
serverCtx := server.NewDefaultContext()
ctx := context.WithValue(context.Background(), server.ServerContextKey, serverCtx)
ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx)
cmd := genutilcli.InitCmd(module.NewBasicManager(), tempDir)
cmd.SetArgs([]string{"appnode-test"})
err = cmd.ExecuteContext(ctx)
require.NoError(t, err)
// Modify app.toml.
appCfgTempFilePath := filepath.Join(tempDir, "config", "app.toml")
appConf := config.DefaultConfig()
appConf.BaseConfig.MinGasPrices = ""
config.WriteConfigFile(appCfgTempFilePath, appConf)
// Run StartCmd.
cmd = server.StartCmd(nil, tempDir)
cmd.PreRunE = func(cmd *cobra.Command, _ []string) error {
return server.InterceptConfigsPreRunHandler(cmd, "", nil, tmcfg.DefaultConfig())
}
err = cmd.ExecuteContext(ctx)
require.Errorf(t, err, sdkerrors.ErrAppConfig.Error())
}
type mapGetter map[string]interface{}
func (m mapGetter) Get(key string) interface{} {
return m[key]
}
var _ servertypes.AppOptions = mapGetter{}