cosmos-sdk/pruning/manager_test.go

537 lines
16 KiB
Go

package pruning_test
import (
"container/list"
"errors"
"fmt"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/log"
db "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/pruning"
"github.com/cosmos/cosmos-sdk/pruning/mock"
"github.com/cosmos/cosmos-sdk/pruning/types"
)
const dbErr = "db error"
func TestNewManager(t *testing.T) {
manager := pruning.NewManager(db.NewMemDB(), log.NewNopLogger())
require.NotNil(t, manager)
heights, err := manager.GetFlushAndResetPruningHeights()
require.NoError(t, err)
require.NotNil(t, heights)
require.Equal(t, types.PruningNothing, manager.GetOptions().GetPruningStrategy())
}
func TestStrategies(t *testing.T) {
testcases := map[string]struct {
strategy types.PruningOptions
snapshotInterval uint64
strategyToAssert types.PruningStrategy
isValid bool
}{
"prune nothing - no snapshot": {
strategy: types.NewPruningOptions(types.PruningNothing),
strategyToAssert: types.PruningNothing,
},
"prune nothing - snapshot": {
strategy: types.NewPruningOptions(types.PruningNothing),
strategyToAssert: types.PruningNothing,
snapshotInterval: 100,
},
"prune default - no snapshot": {
strategy: types.NewPruningOptions(types.PruningDefault),
strategyToAssert: types.PruningDefault,
},
"prune default - snapshot": {
strategy: types.NewPruningOptions(types.PruningDefault),
strategyToAssert: types.PruningDefault,
snapshotInterval: 100,
},
"prune everything - no snapshot": {
strategy: types.NewPruningOptions(types.PruningEverything),
strategyToAssert: types.PruningEverything,
},
"prune everything - snapshot": {
strategy: types.NewPruningOptions(types.PruningEverything),
strategyToAssert: types.PruningEverything,
snapshotInterval: 100,
},
"custom 100-10-15": {
strategy: types.NewCustomPruningOptions(100, 15),
snapshotInterval: 10,
strategyToAssert: types.PruningCustom,
},
"custom 10-10-15": {
strategy: types.NewCustomPruningOptions(10, 15),
snapshotInterval: 10,
strategyToAssert: types.PruningCustom,
},
"custom 100-0-15": {
strategy: types.NewCustomPruningOptions(100, 15),
snapshotInterval: 0,
strategyToAssert: types.PruningCustom,
},
}
manager := pruning.NewManager(db.NewMemDB(), log.NewNopLogger())
require.NotNil(t, manager)
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
curStrategy := tc.strategy
manager.SetSnapshotInterval(tc.snapshotInterval)
pruneStrategy := curStrategy.GetPruningStrategy()
require.Equal(t, tc.strategyToAssert, pruneStrategy)
// Validate strategy parameters
switch pruneStrategy {
case types.PruningDefault:
require.Equal(t, uint64(362880), curStrategy.KeepRecent)
require.Equal(t, uint64(10), curStrategy.Interval)
case types.PruningNothing:
require.Equal(t, uint64(0), curStrategy.KeepRecent)
require.Equal(t, uint64(0), curStrategy.Interval)
case types.PruningEverything:
require.Equal(t, uint64(2), curStrategy.KeepRecent)
require.Equal(t, uint64(10), curStrategy.Interval)
default:
//
}
manager.SetOptions(curStrategy)
require.Equal(t, tc.strategy, manager.GetOptions())
curKeepRecent := curStrategy.KeepRecent
curInterval := curStrategy.Interval
for curHeight := int64(0); curHeight < 110000; curHeight++ {
handleHeightActual := manager.HandleHeight(curHeight)
shouldPruneAtHeightActual := manager.ShouldPruneAtHeight(curHeight)
curPruningHeihts, err := manager.GetFlushAndResetPruningHeights()
require.Nil(t, err)
curHeightStr := fmt.Sprintf("height: %d", curHeight)
switch curStrategy.GetPruningStrategy() {
case types.PruningNothing:
require.Equal(t, int64(0), handleHeightActual, curHeightStr)
require.False(t, shouldPruneAtHeightActual, curHeightStr)
heights, err := manager.GetFlushAndResetPruningHeights()
require.NoError(t, err)
require.Equal(t, 0, len(heights))
default:
if curHeight > int64(curKeepRecent) && (tc.snapshotInterval != 0 && (curHeight-int64(curKeepRecent))%int64(tc.snapshotInterval) != 0 || tc.snapshotInterval == 0) {
expectedHeight := curHeight - int64(curKeepRecent)
require.Equal(t, curHeight-int64(curKeepRecent), handleHeightActual, curHeightStr)
require.Contains(t, curPruningHeihts, expectedHeight, curHeightStr)
} else {
require.Equal(t, int64(0), handleHeightActual, curHeightStr)
heights, err := manager.GetFlushAndResetPruningHeights()
require.NoError(t, err)
require.Equal(t, 0, len(heights))
}
require.Equal(t, curHeight%int64(curInterval) == 0, shouldPruneAtHeightActual, curHeightStr)
}
heights, err := manager.GetFlushAndResetPruningHeights()
require.NoError(t, err)
require.Equal(t, 0, len(heights))
}
})
}
}
func TestHandleHeight_Inputs(t *testing.T) {
var keepRecent int64 = int64(types.NewPruningOptions(types.PruningEverything).KeepRecent)
testcases := map[string]struct {
height int64
expectedResult int64
strategy types.PruningStrategy
expectedHeights []int64
}{
"previousHeight is negative - prune everything - invalid previousHeight": {
-1,
0,
types.PruningEverything,
[]int64{},
},
"previousHeight is zero - prune everything - invalid previousHeight": {
0,
0,
types.PruningEverything,
[]int64{},
},
"previousHeight is positive but within keep recent- prune everything - not kept": {
keepRecent,
0,
types.PruningEverything,
[]int64{},
},
"previousHeight is positive and greater than keep recent - kept": {
keepRecent + 1,
keepRecent + 1 - keepRecent,
types.PruningEverything,
[]int64{keepRecent + 1 - keepRecent},
},
"pruning nothing, previousHeight is positive and greater than keep recent - not kept": {
keepRecent + 1,
0,
types.PruningNothing,
[]int64{},
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
manager := pruning.NewManager(db.NewMemDB(), log.NewNopLogger())
require.NotNil(t, manager)
manager.SetOptions(types.NewPruningOptions(tc.strategy))
handleHeightActual := manager.HandleHeight(tc.height)
require.Equal(t, tc.expectedResult, handleHeightActual)
actualHeights, err := manager.GetFlushAndResetPruningHeights()
require.NoError(t, err)
require.Equal(t, len(tc.expectedHeights), len(actualHeights))
require.Equal(t, tc.expectedHeights, actualHeights)
})
}
}
func TestHandleHeight_FlushLoadFromDisk(t *testing.T) {
testcases := map[string]struct {
previousHeight int64
keepRecent uint64
snapshotInterval uint64
movedSnapshotHeights []int64
expectedHandleHeightResult int64
expectedLoadPruningHeightsResult error
expectedLoadedHeights []int64
}{
"simple flush occurs": {
previousHeight: 11,
keepRecent: 10,
snapshotInterval: 0,
movedSnapshotHeights: []int64{},
expectedHandleHeightResult: 11 - 10,
expectedLoadPruningHeightsResult: nil,
expectedLoadedHeights: []int64{11 - 10},
},
"previous height <= keep recent - no update and no flush": {
previousHeight: 9,
keepRecent: 10,
snapshotInterval: 0,
movedSnapshotHeights: []int64{},
expectedHandleHeightResult: 0,
expectedLoadPruningHeightsResult: nil,
expectedLoadedHeights: []int64{},
},
"previous height alligns with snapshot interval - no update and no flush": {
previousHeight: 12,
keepRecent: 10,
snapshotInterval: 2,
movedSnapshotHeights: []int64{},
expectedHandleHeightResult: 0,
expectedLoadPruningHeightsResult: nil,
expectedLoadedHeights: []int64{},
},
"previous height does not align with snapshot interval - flush": {
previousHeight: 12,
keepRecent: 10,
snapshotInterval: 3,
movedSnapshotHeights: []int64{},
expectedHandleHeightResult: 2,
expectedLoadPruningHeightsResult: nil,
expectedLoadedHeights: []int64{2},
},
"moved snapshot heights - flushed": {
previousHeight: 32,
keepRecent: 10,
snapshotInterval: 5,
movedSnapshotHeights: []int64{15, 20, 25},
expectedHandleHeightResult: 22,
expectedLoadPruningHeightsResult: nil,
expectedLoadedHeights: []int64{15, 20, 22},
},
"previous height alligns with snapshot interval - no update but flush snapshot heights": {
previousHeight: 30,
keepRecent: 10,
snapshotInterval: 5,
movedSnapshotHeights: []int64{15, 20, 25},
expectedHandleHeightResult: 0,
expectedLoadPruningHeightsResult: nil,
expectedLoadedHeights: []int64{15},
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
// Setup
db := db.NewMemDB()
manager := pruning.NewManager(db, log.NewNopLogger())
require.NotNil(t, manager)
manager.SetSnapshotInterval(tc.snapshotInterval)
manager.SetOptions(types.NewCustomPruningOptions(uint64(tc.keepRecent), uint64(10)))
for _, snapshotHeight := range tc.movedSnapshotHeights {
manager.HandleHeightSnapshot(snapshotHeight)
}
// Test HandleHeight and flush
handleHeightActual := manager.HandleHeight(tc.previousHeight)
require.Equal(t, tc.expectedHandleHeightResult, handleHeightActual)
loadedPruneHeights, err := pruning.LoadPruningHeights(db)
require.NoError(t, err)
require.Equal(t, len(loadedPruneHeights), len(loadedPruneHeights))
// Test load back
err = manager.LoadPruningHeights(db)
require.NoError(t, err)
heights, err := manager.GetFlushAndResetPruningHeights()
require.NoError(t, err)
require.Equal(t, len(tc.expectedLoadedHeights), len(heights))
require.ElementsMatch(t, tc.expectedLoadedHeights, heights)
})
}
}
func TestHandleHeight_DbErr_Panic(t *testing.T) {
ctrl := gomock.NewController(t)
// Setup
dbMock := mock.NewMockDB(ctrl)
dbMock.EXPECT().SetSync(gomock.Any(), gomock.Any()).Return(errors.New(dbErr)).Times(1)
manager := pruning.NewManager(dbMock, log.NewNopLogger())
manager.SetOptions(types.NewPruningOptions(types.PruningEverything))
require.NotNil(t, manager)
defer func() {
if r := recover(); r == nil {
t.Fail()
}
}()
manager.HandleHeight(10)
}
func TestHandleHeightSnapshot_FlushLoadFromDisk(t *testing.T) {
loadedHeightsMirror := []int64{}
// Setup
db := db.NewMemDB()
manager := pruning.NewManager(db, log.NewNopLogger())
require.NotNil(t, manager)
manager.SetOptions(types.NewPruningOptions(types.PruningEverything))
for snapshotHeight := int64(-1); snapshotHeight < 100; snapshotHeight++ {
// Test flush
manager.HandleHeightSnapshot(snapshotHeight)
// Post test
if snapshotHeight > 0 {
loadedHeightsMirror = append(loadedHeightsMirror, snapshotHeight)
}
loadedSnapshotHeights, err := pruning.LoadPruningSnapshotHeights(db)
require.NoError(t, err)
require.Equal(t, len(loadedHeightsMirror), loadedSnapshotHeights.Len())
// Test load back
err = manager.LoadPruningHeights(db)
require.NoError(t, err)
loadedSnapshotHeights, err = pruning.LoadPruningSnapshotHeights(db)
require.NoError(t, err)
require.Equal(t, len(loadedHeightsMirror), loadedSnapshotHeights.Len())
}
}
func TestHandleHeightSnapshot_DbErr_Panic(t *testing.T) {
ctrl := gomock.NewController(t)
// Setup
dbMock := mock.NewMockDB(ctrl)
dbMock.EXPECT().SetSync(gomock.Any(), gomock.Any()).Return(errors.New(dbErr)).Times(1)
manager := pruning.NewManager(dbMock, log.NewNopLogger())
manager.SetOptions(types.NewPruningOptions(types.PruningEverything))
require.NotNil(t, manager)
defer func() {
if r := recover(); r == nil {
t.Fail()
}
}()
manager.HandleHeightSnapshot(10)
}
func TestFlushLoad(t *testing.T) {
db := db.NewMemDB()
manager := pruning.NewManager(db, log.NewNopLogger())
require.NotNil(t, manager)
curStrategy := types.NewCustomPruningOptions(100, 15)
snapshotInterval := uint64(10)
manager.SetSnapshotInterval(snapshotInterval)
manager.SetOptions(curStrategy)
require.Equal(t, curStrategy, manager.GetOptions())
keepRecent := curStrategy.KeepRecent
heightsToPruneMirror := make([]int64, 0)
for curHeight := int64(0); curHeight < 1000; curHeight++ {
handleHeightActual := manager.HandleHeight(curHeight)
curHeightStr := fmt.Sprintf("height: %d", curHeight)
if curHeight > int64(keepRecent) && (snapshotInterval != 0 && (curHeight-int64(keepRecent))%int64(snapshotInterval) != 0 || snapshotInterval == 0) {
expectedHandleHeight := curHeight - int64(keepRecent)
require.Equal(t, expectedHandleHeight, handleHeightActual, curHeightStr)
heightsToPruneMirror = append(heightsToPruneMirror, expectedHandleHeight)
} else {
require.Equal(t, int64(0), handleHeightActual, curHeightStr)
}
if manager.ShouldPruneAtHeight(curHeight) && curHeight > int64(keepRecent) {
actualHeights, err := manager.GetFlushAndResetPruningHeights()
require.NoError(t, err)
require.Equal(t, len(heightsToPruneMirror), len(actualHeights))
require.Equal(t, heightsToPruneMirror, actualHeights)
err = manager.LoadPruningHeights(db)
require.NoError(t, err)
actualHeights, err = manager.GetFlushAndResetPruningHeights()
require.NoError(t, err)
require.Equal(t, len(heightsToPruneMirror), len(actualHeights))
require.Equal(t, heightsToPruneMirror, actualHeights)
heightsToPruneMirror = make([]int64, 0)
}
}
}
func TestLoadPruningHeights(t *testing.T) {
var (
manager = pruning.NewManager(db.NewMemDB(), log.NewNopLogger())
err error
)
require.NotNil(t, manager)
// must not be PruningNothing
manager.SetOptions(types.NewPruningOptions(types.PruningDefault))
testcases := map[string]struct {
flushedPruningHeights []int64
getFlushedPruningSnapshotHeights func() *list.List
expectedResult error
}{
"negative pruningHeight - error": {
flushedPruningHeights: []int64{10, 0, -1},
expectedResult: &pruning.NegativeHeightsError{Height: -1},
},
"negative snapshotPruningHeight - error": {
getFlushedPruningSnapshotHeights: func() *list.List {
l := list.New()
l.PushBack(int64(5))
l.PushBack(int64(-2))
l.PushBack(int64(3))
return l
},
expectedResult: &pruning.NegativeHeightsError{Height: -2},
},
"both have negative - pruningHeight error": {
flushedPruningHeights: []int64{10, 0, -1},
getFlushedPruningSnapshotHeights: func() *list.List {
l := list.New()
l.PushBack(int64(5))
l.PushBack(int64(-2))
l.PushBack(int64(3))
return l
},
expectedResult: &pruning.NegativeHeightsError{Height: -1},
},
"both non-negative - success": {
flushedPruningHeights: []int64{10, 0, 3},
getFlushedPruningSnapshotHeights: func() *list.List {
l := list.New()
l.PushBack(int64(5))
l.PushBack(int64(0))
l.PushBack(int64(3))
return l
},
},
}
for name, tc := range testcases {
t.Run(name, func(t *testing.T) {
db := db.NewMemDB()
if tc.flushedPruningHeights != nil {
err = db.Set(pruning.PruneHeightsKey, pruning.Int64SliceToBytes(tc.flushedPruningHeights))
require.NoError(t, err)
}
if tc.getFlushedPruningSnapshotHeights != nil {
err = db.Set(pruning.PruneSnapshotHeightsKey, pruning.ListToBytes(tc.getFlushedPruningSnapshotHeights()))
require.NoError(t, err)
}
err = manager.LoadPruningHeights(db)
require.Equal(t, tc.expectedResult, err)
})
}
}
func TestLoadPruningHeights_PruneNothing(t *testing.T) {
var manager = pruning.NewManager(db.NewMemDB(), log.NewNopLogger())
require.NotNil(t, manager)
manager.SetOptions(types.NewPruningOptions(types.PruningNothing))
require.Nil(t, manager.LoadPruningHeights(db.NewMemDB()))
}
func TestGetFlushAndResetPruningHeights_DbErr_Panic(t *testing.T) {
ctrl := gomock.NewController(t)
// Setup
dbMock := mock.NewMockDB(ctrl)
dbMock.EXPECT().SetSync(gomock.Any(), gomock.Any()).Return(errors.New(dbErr)).Times(1)
manager := pruning.NewManager(dbMock, log.NewNopLogger())
manager.SetOptions(types.NewPruningOptions(types.PruningEverything))
require.NotNil(t, manager)
heights, err := manager.GetFlushAndResetPruningHeights()
require.Error(t, err)
require.Nil(t, heights)
}