Skip to content

Commit

Permalink
#13 Full test coverage for upgrade module
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronc committed Mar 25, 2019
1 parent 2d6d1a4 commit 8f09291
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 19 deletions.
1 change: 1 addition & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ func MakeCodec() *codec.Codec {
group.RegisterCodec(cdc)
proposal.RegisterCodec(cdc)
consortium.RegisterCodec(cdc)
upgrade.RegisterCodec(cdc)
sdk.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
return cdc
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ require (
github.com/spf13/jwalterweatherman v1.0.0 // indirect
github.com/spf13/pflag v1.0.3 // indirect
github.com/spf13/viper v1.0.3
github.com/stretchr/testify v1.2.2
github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2 // indirect
github.com/tendermint/btcd v0.0.0-20180816174608-e5840949ff4f // indirect
github.com/tendermint/go-amino v0.14.1
Expand Down
10 changes: 10 additions & 0 deletions x/upgrade/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package upgrade

import (
"github.com/cosmos/cosmos-sdk/codec"
)

// RegisterCodec registers concrete types on the Amino codec
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(UpgradePlan{}, "upgrade/UpgradePlan", nil)
}
24 changes: 5 additions & 19 deletions x/upgrade/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ type Keeper struct {
cdc *codec.Codec
plan UpgradePlan
haveCachedInfo bool
willUpgrader func(UpgradePlan)
doShutdowner func(sdk.Context, UpgradePlan)
upgradeHandlers map[string]UpgradeHandler
}
Expand Down Expand Up @@ -43,7 +42,7 @@ func (keeper Keeper) ScheduleUpgrade(ctx sdk.Context, plan UpgradePlan) sdk.Erro
return err
}
if !plan.Time.IsZero() {
if plan.Time.Before(ctx.BlockHeader().Time) {
if !plan.Time.After(ctx.BlockHeader().Time) {
return sdk.ErrUnknownRequest("Upgrade cannot be scheduled in the past")
}
} else {
Expand Down Expand Up @@ -79,17 +78,10 @@ func (keeper Keeper) GetUpgradeInfo(ctx sdk.Context, plan *UpgradePlan) sdk.Erro
if bz == nil {
return sdk.ErrUnknownRequest("Not found")
}
marshalErr := keeper.cdc.UnmarshalBinaryBare(bz, &plan)
if marshalErr != nil {
return sdk.ErrUnknownRequest(marshalErr.Error())
}
keeper.cdc.MustUnmarshalBinaryBare(bz, &plan)
return nil
}

func (keeper *Keeper) SetWillUpgrader(willUpgrader func(plan UpgradePlan)) {
keeper.willUpgrader = willUpgrader
}

func (keeper *Keeper) SetDoShutdowner(doShutdowner func(ctx sdk.Context, plan UpgradePlan)) {
keeper.doShutdowner = doShutdowner
}
Expand All @@ -108,19 +100,12 @@ func (keeper *Keeper) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock)
return
}
keeper.haveCachedInfo = true
// TODO verify this will always get called at the right time
if (!keeper.plan.Time.IsZero() && keeper.plan.Time.After(blockTime)) || keeper.plan.Height > blockHeight {
willUpgrader := keeper.willUpgrader
if willUpgrader != nil {
willUpgrader(keeper.plan)
}
}
}

if keeper.haveCachedInfo {
upgradeTime := keeper.plan.Time
upgradeHeight := keeper.plan.Height
if (!upgradeTime.IsZero() && (upgradeTime.Before(blockTime) || upgradeTime.Equal(blockTime))) || upgradeHeight <= blockHeight {
if (!upgradeTime.IsZero() && !blockTime.Before(upgradeTime)) || upgradeHeight <= blockHeight {
handler, ok := keeper.upgradeHandlers[keeper.plan.Name]
if ok {
// We have an upgrade handler for this upgrade name, so apply the upgrade
Expand All @@ -132,11 +117,12 @@ func (keeper *Keeper) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock)
store.Set(upgradeDoneKey(keeper.plan.Name), []byte("1"))
} else {
// We don't have an upgrade handler for this upgrade name, meaning this software is out of date so shutdown
ctx.Logger().Error(fmt.Sprintf("UPGRADE \"%s\" NEEDED needed at height %d: %s", keeper.plan.Name, blockHeight, keeper.plan.Memo))
doShutdowner := keeper.doShutdowner
if doShutdowner != nil {
doShutdowner(ctx, keeper.plan)
} else {
panic(fmt.Sprintf("UPGRADE \"%s\" NEEDED needed at height %d: %s", keeper.plan.Name, blockHeight, keeper.plan.Memo))
panic("UPGRADE REQUIRED!")
}
}
}
Expand Down
141 changes: 141 additions & 0 deletions x/upgrade/keeper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package upgrade

import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
dbm "github.com/tendermint/tendermint/libs/db"
"github.com/tendermint/tendermint/libs/log"
"testing"
"time"
)

type TestSuite struct {
suite.Suite
keeper Keeper
ctx sdk.Context
cms store.CommitMultiStore
}

func (s *TestSuite) SetupTest() {
db := dbm.NewMemDB()
s.cms = store.NewCommitMultiStore(db)
key := sdk.NewKVStoreKey("upgrade")
cdc := codec.New()
RegisterCodec(cdc)
s.keeper = NewKeeper(key, cdc)
s.cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
_ = s.cms.LoadLatestVersion()
s.ctx = sdk.NewContext(s.cms, abci.Header{Height: 10, Time: time.Now()}, false, log.NewNopLogger())
}

func (s *TestSuite) TestRequireName() {
err := s.keeper.ScheduleUpgrade(s.ctx, UpgradePlan{})
s.Require().NotNil(err)
s.Require().Equal(sdk.CodeUnknownRequest, err.Code())
}

func (s *TestSuite) TestRequireFutureTime() {
err := s.keeper.ScheduleUpgrade(s.ctx, UpgradePlan{Name: "test", Time: s.ctx.BlockHeader().Time})
s.Require().NotNil(err)
s.Require().Equal(sdk.CodeUnknownRequest, err.Code())
}

func (s *TestSuite) TestRequireFutureBlock() {
err := s.keeper.ScheduleUpgrade(s.ctx, UpgradePlan{Name: "test", Height: s.ctx.BlockHeight()})
s.Require().NotNil(err)
s.Require().Equal(sdk.CodeUnknownRequest, err.Code())
}

func (s *TestSuite) TestDoTimeUpgrade() {
s.T().Log("Verify can schedule an upgrade")
err := s.keeper.ScheduleUpgrade(s.ctx, UpgradePlan{Name: "test", Time: time.Now()})
s.Require().Nil(err)

s.VerifyDoUpgrade()
}

func (s *TestSuite) TestDoHeightUpgrade() {
s.T().Log("Verify can schedule an upgrade")
err := s.keeper.ScheduleUpgrade(s.ctx, UpgradePlan{Name: "test", Height: s.ctx.BlockHeight() + 1})
s.Require().Nil(err)

s.VerifyDoUpgrade()
}

func (s *TestSuite) VerifyDoUpgrade() {
s.T().Log("Verify that a panic happens at the upgrade time/height")
newCtx := sdk.NewContext(s.cms, abci.Header{Height: s.ctx.BlockHeight() + 1, Time: time.Now()}, false, log.NewNopLogger())
req := abci.RequestBeginBlock{Header: newCtx.BlockHeader()}
s.Require().Panics(func() {
s.keeper.BeginBlocker(newCtx, req)
})

s.T().Log("Verify that the upgrade can be successfully applied with a handler")
s.keeper.SetUpgradeHandler("test", func(ctx sdk.Context, plan UpgradePlan) {})
s.Require().NotPanics(func() {
s.keeper.BeginBlocker(newCtx, req)
})

s.VerifyCleared(newCtx)
}

func (s *TestSuite) VerifyCleared(newCtx sdk.Context) {
s.T().Log("Verify that the upgrade plan has been cleared")
var plan UpgradePlan
err := s.keeper.GetUpgradeInfo(newCtx, &plan)
s.Require().NotNil(err)
s.Require().Equal(sdk.CodeUnknownRequest, err.Code())
}

func (s *TestSuite) TestCanClear() {
s.T().Log("Verify upgrade is scheduled")
err := s.keeper.ScheduleUpgrade(s.ctx, UpgradePlan{Name: "test", Time: time.Now()})
s.Require().Nil(err)

s.keeper.ClearUpgradePlan(s.ctx)

s.VerifyCleared(s.ctx)
}

func (s *TestSuite) TestCantApplySameUpgradeTwice() {
s.TestDoTimeUpgrade()
s.T().Log("Verify an upgrade named \"test\" can't be scheduled twice")
err := s.keeper.ScheduleUpgrade(s.ctx, UpgradePlan{Name: "test", Time: time.Now()})
s.Require().NotNil(err)
s.Require().Equal(sdk.CodeUnknownRequest, err.Code())
}

func (s *TestSuite) TestDoShutdowner() {
s.T().Log("Set a custom DoShutdowner")
shutdownerCalled := false
s.keeper.SetDoShutdowner(func(ctx sdk.Context, plan UpgradePlan) {
shutdownerCalled = true
})

s.T().Log("Run an upgrade and verify that the custom shutdowner was called and no panic happened")
err := s.keeper.ScheduleUpgrade(s.ctx, UpgradePlan{Name: "test", Time: time.Now()})
s.Require().Nil(err)

header := abci.Header{Height: s.ctx.BlockHeight() + 1, Time: time.Now()}
newCtx := sdk.NewContext(s.cms, header, false, log.NewNopLogger())
req := abci.RequestBeginBlock{Header: header}
s.Require().NotPanics(func() {
s.keeper.BeginBlocker(newCtx, req)
})
s.Require().True(shutdownerCalled)
}

func (s *TestSuite) TestNoSpuriousUpgrades() {
s.T().Log("Verify that no upgrade panic is triggered in the BeginBlocker when we haven't scheduled an upgrade")
req := abci.RequestBeginBlock{Header: s.ctx.BlockHeader()}
s.Require().NotPanics(func() {
s.keeper.BeginBlocker(s.ctx, req)
})
}

func TestTestSuite(t *testing.T) {
suite.Run(t, new(TestSuite))
}

0 comments on commit 8f09291

Please sign in to comment.