diff --git a/CHANGELOG.md b/CHANGELOG.md index 075dad97f1..7f0305b056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * Add upgrade handler for 1.18 [#1756](https://github.com/provenance-io/provenance/pull/1756). * Remove the rust upgrade handlers [PR 1774](https://github.com/provenance-io/provenance/pull/1774). +* Allow bypassing the config warning wait using an environment variable [PR 1810](https://github.com/provenance-io/provenance/pull/1810). ### Bug Fixes diff --git a/app/app.go b/app/app.go index d284bf5f3f..8c50610c23 100644 --- a/app/app.go +++ b/app/app.go @@ -1057,7 +1057,7 @@ func New( } // Currently in an upgrade hold for this block. - var storeLoader baseapp.StoreLoader + storeLoader := baseapp.DefaultStoreLoader if upgradeInfo.Name != "" && upgradeInfo.Height == app.LastBlockHeight()+1 { if app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { app.Logger().Info("Skipping upgrade based on height", @@ -1075,7 +1075,6 @@ func New( storeLoader = GetUpgradeStoreLoader(app, upgradeInfo) } } - // -- // Verify configuration settings storeLoader = ValidateWrapper(app.Logger(), appOpts, storeLoader) diff --git a/app/store_loader.go b/app/store_loader.go index 91fd370479..7ad918773f 100644 --- a/app/store_loader.go +++ b/app/store_loader.go @@ -1,8 +1,10 @@ package app import ( - "errors" "fmt" + "os" + "strconv" + "strings" "time" "github.com/spf13/cast" @@ -16,60 +18,79 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// StoreLoaderWrapper is a wrapper function that is called before the StoreLoader. -type StoreLoaderWrapper func(sdk.CommitMultiStore, baseapp.StoreLoader) error +// ValidateWrapperSleeper is the sleeper that the ValidateWrapper will use. +// It primarily exists so it can be changed for unit tests on ValidateWrapper so they don't take so long. +var ValidateWrapperSleeper Sleeper = &DefaultSleeper{} -// WrapStoreLoader creates a new StoreLoader by wrapping an existing one. -func WrapStoreLoader(wrapper StoreLoaderWrapper, storeLoader baseapp.StoreLoader) baseapp.StoreLoader { +// ValidateWrapper creates a new StoreLoader that first checks the config settings before calling the provided StoreLoader. +func ValidateWrapper(logger log.Logger, appOpts servertypes.AppOptions, storeLoader baseapp.StoreLoader) baseapp.StoreLoader { return func(ms sdk.CommitMultiStore) error { - if storeLoader == nil { - storeLoader = baseapp.DefaultStoreLoader - } + IssueConfigWarnings(logger, appOpts, ValidateWrapperSleeper) + return storeLoader(ms) + } +} - if wrapper == nil { - return errors.New("wrapper must not be nil") - } +// Sleeper is an interface for something with a Sleep function. +type Sleeper interface { + Sleep(d time.Duration) +} - return wrapper(ms, storeLoader) - } +// DefaultSleeper uses the time.Sleep function for sleeping. +type DefaultSleeper struct{} + +// Sleep is a wrapper for time.Sleep(d). +func (s DefaultSleeper) Sleep(d time.Duration) { + time.Sleep(d) } -// ValidateWrapper creates a new StoreLoader that first checks the config settings before calling the provided StoreLoader. -func ValidateWrapper(logger log.Logger, appOpts servertypes.AppOptions, storeLoader baseapp.StoreLoader) baseapp.StoreLoader { - return WrapStoreLoader(func(ms sdk.CommitMultiStore, sl baseapp.StoreLoader) error { - const MaxPruningInterval = 999 - const SleepSeconds = 30 - backend := server.GetAppDBBackend(appOpts) - interval := cast.ToUint64(appOpts.Get("pruning-interval")) - txIndexer := cast.ToStringMap(appOpts.Get("tx_index")) - indexer := cast.ToString(txIndexer["indexer"]) - fastNode := cast.ToBool(appOpts.Get("iavl-disable-fastnode")) - var errs []string - - if interval > MaxPruningInterval { - errs = append(errs, fmt.Sprintf("pruning-interval %d EXCEEDS %d AND IS NOT RECOMMENDED, AS IT CAN LEAD TO MISSED BLOCKS ON VALIDATORS", interval, MaxPruningInterval)) - } +// IssueConfigWarnings checks a few values in the configs and issues warnings and sleeps if appropriate. +func IssueConfigWarnings(logger log.Logger, appOpts servertypes.AppOptions, sleeper Sleeper) { + const MaxPruningInterval = 999 + const SleepSeconds = 30 + interval := cast.ToUint64(appOpts.Get("pruning-interval")) + txIndexer := cast.ToStringMap(appOpts.Get("tx_index")) + indexer := cast.ToString(txIndexer["indexer"]) + fastNode := cast.ToBool(appOpts.Get(server.FlagDisableIAVLFastNode)) + backend := server.GetAppDBBackend(appOpts) + var errs []string - if indexer != "" && indexer != "null" { - errs = append(errs, fmt.Sprintf("indexer \"%s\" IS NOT RECOMMENDED, AND IT IS RECOMMENDED TO USE \"%s\"", indexer, "null")) - } + if interval > MaxPruningInterval { + errs = append(errs, fmt.Sprintf("pruning-interval %d EXCEEDS %d AND IS NOT RECOMMENDED, AS IT CAN LEAD TO MISSED BLOCKS ON VALIDATORS.", interval, MaxPruningInterval)) + } - if fastNode { - errs = append(errs, fmt.Sprintf("iavl-disable-fastnode \"%v\" IS NOT RECOMMENDED, AND IT IS RECOMMENDED TO USE \"%v\"", fastNode, !fastNode)) - } + if indexer != "" && indexer != "null" { + errs = append(errs, fmt.Sprintf("indexer \"%s\" IS NOT RECOMMENDED, AND IT IS RECOMMENDED TO USE \"%s\".", indexer, "null")) + } - if backend != dbm.GoLevelDBBackend { - errs = append(errs, fmt.Sprintf("%s IS NO LONGER SUPPORTED. MIGRATE TO %s", backend, dbm.GoLevelDBBackend)) - } + if fastNode { + errs = append(errs, fmt.Sprintf("%s \"%v\" IS NOT RECOMMENDED, AND IT IS RECOMMENDED TO USE \"%v\".", server.FlagDisableIAVLFastNode, fastNode, !fastNode)) + } + + if backend != dbm.GoLevelDBBackend { + errs = append(errs, fmt.Sprintf("%s IS NO LONGER SUPPORTED. MIGRATE TO %s.", backend, dbm.GoLevelDBBackend)) + } - if len(errs) > 0 { - logger.Error(fmt.Sprintf("NODE WILL CONTINUE AFTER %d SECONDS", SleepSeconds)) - for _, err := range errs { - logger.Error(err) - } - time.Sleep(SleepSeconds * time.Second) + if len(errs) > 0 { + for _, err := range errs { + logger.Error(err) + } + if !HaveAckWarn() { + logger.Error(fmt.Sprintf("NODE WILL CONTINUE AFTER %d SECONDS.", SleepSeconds)) + logger.Error("This wait can be bypassed by fixing the above warnings or setting the PIO_ACKWARN environment variable to \"1\".") + sleeper.Sleep(SleepSeconds * time.Second) } + } +} + +// HaveAckWarn returns true if the PIO_ACKWARN env var is set and isn't a false value (e.g. "0", "f" or "false"). +func HaveAckWarn() bool { + ackWarn := strings.TrimSpace(os.Getenv("PIO_ACKWARN")) + if len(ackWarn) == 0 { + return false + } - return sl(ms) - }, storeLoader) + rv, err := strconv.ParseBool(ackWarn) + // We return false only if it parsed successfully to a false value. + // If parsing failed or it parsed to a true value, we return true. + return err != nil || rv } diff --git a/app/store_loader_test.go b/app/store_loader_test.go index b337ab7417..a1fed24c49 100644 --- a/app/store_loader_test.go +++ b/app/store_loader_test.go @@ -1,208 +1,387 @@ package app import ( + "bytes" + "errors" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/libs/log" - dbm "github.com/tendermint/tm-db" - - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/store/rootmulti" + "github.com/cosmos/cosmos-sdk/server" sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/provenance-io/provenance/internal" + "github.com/provenance-io/provenance/testutil/assertions" ) -func TestWrapStoreLoader(t *testing.T) { - var flag bool +// StoreLoaderMocker is a struct with a StoreLoader method that records that the store loader was called and returns a pre-determined error message. +type StoreLoaderMocker struct { + Called bool + ErrMsg string +} + +func NewStoreLoaderMocker(errMsg string) *StoreLoaderMocker { + return &StoreLoaderMocker{ + ErrMsg: errMsg, + } +} + +func (s *StoreLoaderMocker) StoreLoader(_ sdk.CommitMultiStore) error { + s.Called = true + if len(s.ErrMsg) > 0 { + return errors.New(s.ErrMsg) + } + return nil +} + +// MockSleeper is a Sleeper that only records what sleep was requested (instead of actually sleeping). +type MockSleeper struct { + LastSleep time.Duration +} + +var _ Sleeper = (*MockSleeper)(nil) + +func NewMockSleeper() *MockSleeper { + return &MockSleeper{} +} + +func (s *MockSleeper) Sleep(d time.Duration) { + s.LastSleep = d +} + +// MockAppOptions is a mocked version of AppOpts that allows the developer to provide the pruning attribute. +type MockAppOptions struct { + pruning string + indexer string + db string + fastNode string +} + +// Get returns the value for the provided option. +func (m MockAppOptions) Get(opt string) interface{} { + switch opt { + case "pruning-interval": + return m.pruning + case "tx_index": + return map[string]interface{}{ + "indexer": m.indexer, + } + case "app-db-backend": + return m.db + case "db-backend": + return m.db + case server.FlagDisableIAVLFastNode: + return m.fastNode + } + + return nil +} + +func TestValidateWrapper(t *testing.T) { + defer func() { + ValidateWrapperSleeper = &DefaultSleeper{} + }() + + recAppOpts := MockAppOptions{ + pruning: "10", + db: "goleveldb", + fastNode: "false", + indexer: "null", + } + tests := []struct { - name string - storeLoader baseapp.StoreLoader - wrapper StoreLoaderWrapper - err string + name string + appOpts MockAppOptions + pioAckWarn bool + expErr string + expLogMsgs bool + expSleep bool }{ { - name: "nil store loader is set with valid value", - storeLoader: nil, - wrapper: createMockStoreWrapper(&flag), + name: "empty opts", + appOpts: MockAppOptions{}, + expErr: "", + expLogMsgs: false, + expSleep: false, }, { - name: "nil wrapper is handled", - storeLoader: createMockStoreLoader(), - wrapper: nil, - err: "wrapper must not be nil", + name: "bad config", + appOpts: MockAppOptions{ + db: "cleveldb", + }, + expLogMsgs: true, + expSleep: true, }, { - name: "contents of wrapper are called", - storeLoader: createMockStoreLoader(), - wrapper: createMockFlipWrapper(&flag), + name: "bad config no sleep", + appOpts: MockAppOptions{ + db: "cleveldb", + }, + pioAckWarn: true, + expLogMsgs: true, + expSleep: false, + }, + { + name: "err from store loader", + appOpts: recAppOpts, + expErr: "injected test error", + }, + { + name: "bad config and err from store loader", + appOpts: MockAppOptions{ + fastNode: "true", + }, + expErr: "another injected error for testing", + expLogMsgs: true, + expSleep: true, + }, + { + name: "bad config and err from store loader no sleep", + appOpts: MockAppOptions{ + fastNode: "true", + }, + pioAckWarn: true, + expErr: "another injected error for testing", + expLogMsgs: true, + expSleep: false, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - storeLoader := WrapStoreLoader(tc.wrapper, tc.storeLoader) - db := dbm.MemDB{} - ms := rootmulti.NewStore(&db, nil) - assert.NotNil(t, ms, "should create a new multistore for testing") - flag = false - - err := storeLoader(ms) - if len(tc.err) > 0 { - assert.EqualError(t, err, tc.err, "should have correct error") - assert.False(t, flag, "wrapper should not be executed") + sleeper := NewMockSleeper() + ValidateWrapperSleeper = sleeper + + if tc.pioAckWarn { + t.Setenv("PIO_ACKWARN", "1") + } + + var buffer bytes.Buffer + logger := internal.NewBufferedInfoLogger(&buffer) + slMocker := NewStoreLoaderMocker(tc.expErr) + storeLoader := ValidateWrapper(logger, tc.appOpts, slMocker.StoreLoader) + + var err error + testFunc := func() { + err = storeLoader(nil) + } + require.NotPanics(t, testFunc, "calling the storeLoader that was returned by ValidateWrapper") + assertions.AssertErrorValue(t, err, tc.expErr, "error from storeLoader") + + logMsgs := buffer.String() + if tc.expLogMsgs { + assert.NotEmpty(t, logMsgs, "messages logged during storeLoader") } else { - assert.NoError(t, err, "should not return an error on success") - assert.True(t, flag, "wrapper should execute and have correct logic") + assert.Empty(t, logMsgs, "messages logged during storeLoader") } + didSleep := sleeper.LastSleep != 0 + assert.Equal(t, tc.expSleep, didSleep, "whether sleep was called") }) } } -func TestValidateWrapper(t *testing.T) { +func TestIssueConfigWarnings(t *testing.T) { + sleepErr1 := "ERR NODE WILL CONTINUE AFTER 30 SECONDS." + sleepErr2 := "ERR This wait can be bypassed by fixing the above warnings or setting the PIO_ACKWARN environment variable to \"1\"." + tests := []struct { - name string - appOpts MockAppOptions - delta uint64 + name string + appOpts MockAppOptions + pioAckWarn string + expLogLines []string // can be in any order, but all must be there. + expSleep bool }{ { - name: "recommended pruning, indexer, db, and fastnode should not wait", + name: "no app opts", + appOpts: MockAppOptions{}, + expLogLines: nil, + }, + { + name: "recommended app opts", appOpts: MockAppOptions{ - pruning: "13", + pruning: "10", db: "goleveldb", fastNode: "false", indexer: "null", }, - delta: 0, + expLogLines: nil, }, { - name: "recommended pruning, db, and fastnode and empty indexer should not wait", + name: "bad pruning interval", appOpts: MockAppOptions{ - pruning: "13", + pruning: "1000", db: "goleveldb", fastNode: "false", - indexer: "", + indexer: "null", }, - delta: 0, + expLogLines: []string{ + "ERR pruning-interval 1000 EXCEEDS 999 AND IS NOT RECOMMENDED, AS IT CAN LEAD TO MISSED BLOCKS ON VALIDATORS.", + sleepErr1, + sleepErr2, + }, + expSleep: true, }, { - name: "non-recommended pruning should wait", + name: "bad indexer", appOpts: MockAppOptions{ - pruning: "1000", + pruning: "10", db: "goleveldb", fastNode: "false", - indexer: "", + indexer: "kv", }, - delta: 30, + expLogLines: []string{ + "ERR indexer \"kv\" IS NOT RECOMMENDED, AND IT IS RECOMMENDED TO USE \"null\".", + sleepErr1, + sleepErr2, + }, + expSleep: true, }, { - name: "non-recommended indexer should wait", + name: "bad fastnode", appOpts: MockAppOptions{ - pruning: "13", + pruning: "10", db: "goleveldb", - fastNode: "false", - indexer: "kv", + fastNode: "true", + indexer: "null", + }, + expLogLines: []string{ + "ERR iavl-disable-fastnode \"true\" IS NOT RECOMMENDED, AND IT IS RECOMMENDED TO USE \"false\".", + sleepErr1, + sleepErr2, }, - delta: 30, + expSleep: true, }, { - name: "non-recommended db should wait", + name: "bad db", appOpts: MockAppOptions{ - pruning: "13", + pruning: "10", db: "cleveldb", fastNode: "false", - indexer: "", + indexer: "null", + }, + expLogLines: []string{ + "ERR cleveldb IS NO LONGER SUPPORTED. MIGRATE TO goleveldb.", + sleepErr1, + sleepErr2, }, - delta: 30, + expSleep: true, }, { - name: "non-recommended fastnode should wait", + name: "all bad with sleep", appOpts: MockAppOptions{ - pruning: "13", - db: "goleveldb", + pruning: "1001", + db: "badgerdb", fastNode: "true", - indexer: "", + indexer: "psql", }, - delta: 30, + expLogLines: []string{ + "ERR pruning-interval 1001 EXCEEDS 999 AND IS NOT RECOMMENDED, AS IT CAN LEAD TO MISSED BLOCKS ON VALIDATORS.", + "ERR indexer \"psql\" IS NOT RECOMMENDED, AND IT IS RECOMMENDED TO USE \"null\".", + "ERR iavl-disable-fastnode \"true\" IS NOT RECOMMENDED, AND IT IS RECOMMENDED TO USE \"false\".", + "ERR badgerdb IS NO LONGER SUPPORTED. MIGRATE TO goleveldb.", + sleepErr1, + sleepErr2, + }, + expSleep: true, }, { - name: "multiple non-recommended should wait", + name: "all bad no sleep", appOpts: MockAppOptions{ - pruning: "1000", - db: "cleveldb", + pruning: "1001", + db: "badgerdb", fastNode: "true", - indexer: "kv", + indexer: "psql", }, - delta: 30, + pioAckWarn: "1", + expLogLines: []string{ + "ERR pruning-interval 1001 EXCEEDS 999 AND IS NOT RECOMMENDED, AS IT CAN LEAD TO MISSED BLOCKS ON VALIDATORS.", + "ERR indexer \"psql\" IS NOT RECOMMENDED, AND IT IS RECOMMENDED TO USE \"null\".", + "ERR iavl-disable-fastnode \"true\" IS NOT RECOMMENDED, AND IT IS RECOMMENDED TO USE \"false\".", + "ERR badgerdb IS NO LONGER SUPPORTED. MIGRATE TO goleveldb.", + }, + expSleep: false, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - logger := log.NewNopLogger() - storeLoader := ValidateWrapper(logger, tc.appOpts, createMockStoreLoader()) - db := dbm.MemDB{} - ms := rootmulti.NewStore(&db, nil) - assert.NotNil(t, ms, "should create a new multistore for testing") - - start := time.Now() - err := storeLoader(ms) - delta := uint64(time.Now().Sub(start).Seconds()) - assert.NoError(t, err, "should not throw error") - assert.GreaterOrEqual(t, delta, tc.delta, "should wait correct amount of time") - }) - } -} + var expSleepDur time.Duration + if tc.expSleep { + expSleepDur = 30 * time.Second + } -// createMockStoreLoader creates an empty StoreLoader. -func createMockStoreLoader() baseapp.StoreLoader { - return func(ms sdk.CommitMultiStore) error { - return nil - } -} + if len(tc.pioAckWarn) > 0 { + t.Setenv("PIO_ACKWARN", tc.pioAckWarn) + } + var buffer bytes.Buffer + logger := internal.NewBufferedInfoLogger(&buffer) + sleeper := NewMockSleeper() -// createMockFlipWrapper creates a wrapper that has logic to flip a bit. -func createMockFlipWrapper(flag *bool) StoreLoaderWrapper { - return func(cms sdk.CommitMultiStore, sl baseapp.StoreLoader) error { - *flag = !(*flag) - return nil - } -} + testFunc := func() { + IssueConfigWarnings(logger, tc.appOpts, sleeper) + } + require.NotPanics(t, testFunc, "IssueConfigWarnings") -// createMockStoreWrapper creates a wrapper that checks if the StoreLoader is nil and sets the flag accordingly. -func createMockStoreWrapper(flag *bool) StoreLoaderWrapper { - return func(cms sdk.CommitMultiStore, sl baseapp.StoreLoader) error { - *flag = sl != nil - return nil + loggedLines := internal.SplitLogLines(buffer.String()) + assert.ElementsMatch(t, tc.expLogLines, loggedLines, "Lines logged during IssueConfigWarnings. List A is the expected lines.") + actSleepDur := sleeper.LastSleep + assert.Equal(t, expSleepDur.String(), actSleepDur.String(), "sleep duration during IssueConfigWarnings") + }) } } -// MockAppOptions is a mocked version of AppOpts that allows the developer to provide the pruning attribute. -type MockAppOptions struct { - pruning string - indexer string - db string - fastNode string -} +func TestHaveAckWarn(t *testing.T) { + tests := []struct { + pioAckWarn string + noPioAckWarn bool + expected bool + }{ + {noPioAckWarn: true, expected: false}, + {pioAckWarn: "", expected: false}, + {pioAckWarn: " ", expected: false}, + {pioAckWarn: "0", expected: false}, + {pioAckWarn: " 0 ", expected: false}, + {pioAckWarn: "false", expected: false}, + {pioAckWarn: " False", expected: false}, + {pioAckWarn: "FALSE ", expected: false}, + {pioAckWarn: "f", expected: false}, + {pioAckWarn: " F ", expected: false}, -// Get returns the value for the provided option. -func (m MockAppOptions) Get(opt string) interface{} { - switch opt { - case "pruning-interval": - return m.pruning - case "tx_index": - return map[string]interface{}{ - "indexer": m.indexer, - } - case "app-db-backend": - return m.db - case "db-backend": - return m.db - case "iavl-disable-fastnode": - return m.fastNode + {pioAckWarn: "1", expected: true}, + {pioAckWarn: "yes", expected: true}, + {pioAckWarn: "t", expected: true}, + {pioAckWarn: "true", expected: true}, + {pioAckWarn: " T", expected: true}, + {pioAckWarn: "TRUE ", expected: true}, + {pioAckWarn: " True ", expected: true}, + {pioAckWarn: "whatever", expected: true}, + {pioAckWarn: "X", expected: true}, + {pioAckWarn: "ff", expected: true}, + {pioAckWarn: "farse", expected: true}, } - return nil + for _, tc := range tests { + name := tc.pioAckWarn + if tc.noPioAckWarn { + name = "no PIO_ACKWARN set" + } + if len(name) == 0 { + name = "empty string" + } + + t.Run(name, func(t *testing.T) { + if !tc.noPioAckWarn { + t.Setenv("PIO_ACKWARN", tc.pioAckWarn) + } + var actual bool + testFunc := func() { + actual = HaveAckWarn() + } + require.NotPanics(t, testFunc, "HaveAckWarn") + assert.Equal(t, tc.expected, actual, "HaveAckWarn result") + }) + } } diff --git a/app/test_helpers.go b/app/test_helpers.go index 848982362e..692aea980c 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -41,6 +41,7 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/provenance-io/provenance/app/params" + "github.com/provenance-io/provenance/internal" "github.com/provenance-io/provenance/internal/pioconfig" rewardtypes "github.com/provenance-io/provenance/x/reward/types" ) @@ -132,6 +133,15 @@ func NewInfoLogger() log.Logger { return server.ZeroLogWrapper{Logger: logger} } +// BufferedInfoLoggerMaker returns a logger maker function for a NewBufferedInfoLogger. +// Error log lines will start with "ERR ". +// Info log lines will start with "INF ". +func BufferedInfoLoggerMaker(buffer *bytes.Buffer) LoggerMakerFn { + return func() log.Logger { + return internal.NewBufferedInfoLogger(buffer) + } +} + // NewAppWithCustomOptions initializes a new SimApp with custom options. func NewAppWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOptions) *App { t.Helper() diff --git a/app/upgrades_test.go b/app/upgrades_test.go index c82ee3f658..a431946155 100644 --- a/app/upgrades_test.go +++ b/app/upgrades_test.go @@ -9,14 +9,11 @@ import ( "testing" "time" - "github.com/rs/zerolog" "github.com/stretchr/testify/suite" - "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - "github.com/cosmos/cosmos-sdk/server" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/bank/testutil" @@ -45,19 +42,7 @@ func TestUpgradeTestSuite(t *testing.T) { func (s *UpgradeTestSuite) SetupSuite() { // Alert: This function is SetupSuite. That means all tests in here // will use the same app with the same store and data. - bufferedLoggerMaker := func() log.Logger { - lw := zerolog.ConsoleWriter{ - Out: &s.logBuffer, - NoColor: true, - PartsExclude: []string{"time"}, // Without this, each line starts with " " - } - // Error log lines will start with "ERR ". - // Info log lines will start with "INF ". - // Debug log lines are omitted, but would start with "DBG ". - logger := zerolog.New(lw).Level(zerolog.InfoLevel) - return server.ZeroLogWrapper{Logger: logger} - } - defer SetLoggerMaker(SetLoggerMaker(bufferedLoggerMaker)) + defer SetLoggerMaker(SetLoggerMaker(BufferedInfoLoggerMaker(&s.logBuffer))) s.app = Setup(s.T()) s.logBuffer.Reset() s.startTime = time.Now() diff --git a/internal/logger.go b/internal/logger.go new file mode 100644 index 0000000000..b292a611ae --- /dev/null +++ b/internal/logger.go @@ -0,0 +1,48 @@ +package internal + +import ( + "bytes" + "strings" + + "github.com/rs/zerolog" + + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/server" +) + +// NewBufferedLogger creates a new logger that writes to the provided buffer. +// Error log lines will start with "ERR ". +// Info log lines will start with "INF ". +// Debug log lines will start with "DBG ". +func NewBufferedLogger(buffer *bytes.Buffer, level zerolog.Level) log.Logger { + lw := zerolog.ConsoleWriter{ + Out: buffer, + NoColor: true, + PartsExclude: []string{"time"}, // Without this, each line starts with " " + } + logger := zerolog.New(lw).Level(level) + return server.ZeroLogWrapper{Logger: logger} +} + +// NewBufferedInfoLogger creates a new logger with level info that writes to the provided buffer. +// Error log lines will start with "ERR ". +// Info log lines will start with "INF ". +// Debug log lines are omitted, but would start with "DBG ". +func NewBufferedInfoLogger(buffer *bytes.Buffer) log.Logger { + return NewBufferedLogger(buffer, zerolog.InfoLevel) +} + +// SplitLogLines splits the provided logs string into its individual lines. +func SplitLogLines(logs string) []string { + rv := strings.Split(logs, "\n") + // Trim spaces from each line. + for i, line := range rv { + rv[i] = strings.TrimSpace(line) + } + // Remove empty lines from the end (at least one gets added due to a final newline in the logs). + for len(rv) > 0 && len(rv[len(rv)-1]) == 0 { + rv = rv[:len(rv)-1] + } + return rv +} diff --git a/x/attribute/keeper/genesis_test.go b/x/attribute/keeper/genesis_test.go index 2ac2c0b28a..79a6a0b8c6 100644 --- a/x/attribute/keeper/genesis_test.go +++ b/x/attribute/keeper/genesis_test.go @@ -5,14 +5,11 @@ import ( "fmt" "testing" - "github.com/rs/zerolog" "github.com/stretchr/testify/suite" - "github.com/cosmos/cosmos-sdk/server" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/provenance-io/provenance/x/attribute/keeper" - "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/provenance-io/provenance/app" @@ -36,19 +33,7 @@ func TestGenesisTestSuite(t *testing.T) { func (s *GenesisTestSuite) SetupSuite() { // Alert: This function is SetupSuite. That means all tests in here // will use the same app with the same store and data. - bufferedLoggerMaker := func() log.Logger { - lw := zerolog.ConsoleWriter{ - Out: &s.logBuffer, - NoColor: true, - PartsExclude: []string{"time"}, // Without this, each line starts with " " - } - // Error log lines will start with "ERR ". - // Info log lines will start with "INF ". - // Debug log lines are omitted, but would start with "DBG ". - logger := zerolog.New(lw).Level(zerolog.InfoLevel) - return server.ZeroLogWrapper{Logger: logger} - } - defer app.SetLoggerMaker(app.SetLoggerMaker(bufferedLoggerMaker)) + defer app.SetLoggerMaker(app.SetLoggerMaker(app.BufferedInfoLoggerMaker(&s.logBuffer))) s.app = app.Setup(s.T()) s.logBuffer.Reset() s.ctx = s.app.BaseApp.NewContext(false, tmproto.Header{}) diff --git a/x/exchange/keeper/suite_test.go b/x/exchange/keeper/suite_test.go index 42cbd76985..36d4e8da23 100644 --- a/x/exchange/keeper/suite_test.go +++ b/x/exchange/keeper/suite_test.go @@ -8,13 +8,10 @@ import ( "testing" "github.com/gogo/protobuf/proto" - "github.com/rs/zerolog" "github.com/stretchr/testify/suite" - "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" - "github.com/cosmos/cosmos-sdk/server" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -58,20 +55,8 @@ func (s *TestSuite) SetupTest() { s.addrLookupMap = make(map[string]string) } - bufferedLoggerMaker := func() log.Logger { - lw := zerolog.ConsoleWriter{ - Out: &s.logBuffer, - NoColor: true, - PartsExclude: []string{"time"}, // Without this, each line starts with " " - } - // Error log lines will start with "ERR ". - // Info log lines will start with "INF ". - // Debug log lines are omitted, but would start with "DBG ". - logger := zerolog.New(lw).Level(zerolog.InfoLevel) - return server.ZeroLogWrapper{Logger: logger} - } // swap in the buffered logger maker so it's used in app.Setup, but then put it back (since that's a global thing). - defer app.SetLoggerMaker(app.SetLoggerMaker(bufferedLoggerMaker)) + defer app.SetLoggerMaker(app.SetLoggerMaker(app.BufferedInfoLoggerMaker(&s.logBuffer))) s.app = app.Setup(s.T()) s.logBuffer.Reset()