cosmos-sdk/snapshots/store_test.go

336 lines
9.1 KiB
Go

package snapshots_test
import (
"bytes"
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
db "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/snapshots"
"github.com/cosmos/cosmos-sdk/snapshots/types"
"github.com/cosmos/cosmos-sdk/testutil"
)
func setupStore(t *testing.T) *snapshots.Store {
// ioutil.TempDir() is used instead of testing.T.TempDir()
// see https://github.com/cosmos/cosmos-sdk/pull/8475 for
// this change's rationale.
tempdir, err := ioutil.TempDir("", "")
require.NoError(t, err)
t.Cleanup(func() { _ = os.RemoveAll(tempdir) })
store, err := snapshots.NewStore(db.NewMemDB(), tempdir)
require.NoError(t, err)
_, err = store.Save(1, 1, makeChunks([][]byte{
{1, 1, 0}, {1, 1, 1},
}))
require.NoError(t, err)
_, err = store.Save(2, 1, makeChunks([][]byte{
{2, 1, 0}, {2, 1, 1},
}))
require.NoError(t, err)
_, err = store.Save(2, 2, makeChunks([][]byte{
{2, 2, 0}, {2, 2, 1}, {2, 2, 2},
}))
require.NoError(t, err)
_, err = store.Save(3, 2, makeChunks([][]byte{
{3, 2, 0}, {3, 2, 1}, {3, 2, 2},
}))
require.NoError(t, err)
return store
}
func TestNewStore(t *testing.T) {
tempdir := t.TempDir()
_, err := snapshots.NewStore(db.NewMemDB(), tempdir)
require.NoError(t, err)
}
func TestNewStore_ErrNoDir(t *testing.T) {
_, err := snapshots.NewStore(db.NewMemDB(), "")
require.Error(t, err)
}
func TestNewStore_ErrDirFailure(t *testing.T) {
notADir := filepath.Join(testutil.TempFile(t).Name(), "subdir")
_, err := snapshots.NewStore(db.NewMemDB(), notADir)
require.Error(t, err)
}
func TestStore_Delete(t *testing.T) {
store := setupStore(t)
// Deleting a snapshot should remove it
err := store.Delete(2, 2)
require.NoError(t, err)
snapshot, err := store.Get(2, 2)
require.NoError(t, err)
assert.Nil(t, snapshot)
snapshots, err := store.List()
require.NoError(t, err)
assert.Len(t, snapshots, 3)
// Deleting it again should not error
err = store.Delete(2, 2)
require.NoError(t, err)
// Deleting a snapshot being saved should error
ch := make(chan io.ReadCloser)
go store.Save(9, 1, ch)
time.Sleep(10 * time.Millisecond)
err = store.Delete(9, 1)
require.Error(t, err)
// But after it's saved it should work
close(ch)
time.Sleep(10 * time.Millisecond)
err = store.Delete(9, 1)
require.NoError(t, err)
}
func TestStore_Get(t *testing.T) {
store := setupStore(t)
// Loading a missing snapshot should return nil
snapshot, err := store.Get(9, 9)
require.NoError(t, err)
assert.Nil(t, snapshot)
// Loading a snapshot should returns its metadata
snapshot, err = store.Get(2, 1)
require.NoError(t, err)
assert.Equal(t, &types.Snapshot{
Height: 2,
Format: 1,
Chunks: 2,
Hash: hash([][]byte{{2, 1, 0}, {2, 1, 1}}),
Metadata: types.Metadata{
ChunkHashes: checksums([][]byte{
{2, 1, 0}, {2, 1, 1}}),
},
}, snapshot)
}
func TestStore_GetLatest(t *testing.T) {
store := setupStore(t)
// Loading a missing snapshot should return nil
snapshot, err := store.GetLatest()
require.NoError(t, err)
assert.Equal(t, &types.Snapshot{
Height: 3,
Format: 2,
Chunks: 3,
Hash: hash([][]byte{
{3, 2, 0},
{3, 2, 1},
{3, 2, 2},
}),
Metadata: types.Metadata{
ChunkHashes: checksums([][]byte{
{3, 2, 0},
{3, 2, 1},
{3, 2, 2},
}),
},
}, snapshot)
}
func TestStore_List(t *testing.T) {
store := setupStore(t)
snapshots, err := store.List()
require.NoError(t, err)
require.Equal(t, []*types.Snapshot{
{Height: 3, Format: 2, Chunks: 3, Hash: hash([][]byte{{3, 2, 0}, {3, 2, 1}, {3, 2, 2}}),
Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{3, 2, 0}, {3, 2, 1}, {3, 2, 2}})},
},
{Height: 2, Format: 2, Chunks: 3, Hash: hash([][]byte{{2, 2, 0}, {2, 2, 1}, {2, 2, 2}}),
Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{2, 2, 0}, {2, 2, 1}, {2, 2, 2}})},
},
{Height: 2, Format: 1, Chunks: 2, Hash: hash([][]byte{{2, 1, 0}, {2, 1, 1}}),
Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{2, 1, 0}, {2, 1, 1}})},
},
{Height: 1, Format: 1, Chunks: 2, Hash: hash([][]byte{{1, 1, 0}, {1, 1, 1}}),
Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{1, 1, 0}, {1, 1, 1}})},
},
}, snapshots)
}
func TestStore_Load(t *testing.T) {
store := setupStore(t)
// Loading a missing snapshot should return nil
snapshot, chunks, err := store.Load(9, 9)
require.NoError(t, err)
assert.Nil(t, snapshot)
assert.Nil(t, chunks)
// Loading a snapshot should returns its metadata and chunks
snapshot, chunks, err = store.Load(2, 1)
require.NoError(t, err)
assert.Equal(t, &types.Snapshot{
Height: 2,
Format: 1,
Chunks: 2,
Hash: hash([][]byte{{2, 1, 0}, {2, 1, 1}}),
Metadata: types.Metadata{
ChunkHashes: checksums([][]byte{
{2, 1, 0}, {2, 1, 1}}),
},
}, snapshot)
for i := uint32(0); i < snapshot.Chunks; i++ {
reader, ok := <-chunks
require.True(t, ok)
chunk, err := ioutil.ReadAll(reader)
require.NoError(t, err)
err = reader.Close()
require.NoError(t, err)
assert.Equal(t, []byte{2, 1, byte(i)}, chunk)
}
assert.Empty(t, chunks)
}
func TestStore_LoadChunk(t *testing.T) {
store := setupStore(t)
// Loading a missing snapshot should return nil
chunk, err := store.LoadChunk(9, 9, 0)
require.NoError(t, err)
assert.Nil(t, chunk)
// Loading a missing chunk index should return nil
chunk, err = store.LoadChunk(2, 1, 2)
require.NoError(t, err)
require.Nil(t, chunk)
// Loading a chunk should returns a content reader
chunk, err = store.LoadChunk(2, 1, 0)
require.NoError(t, err)
require.NotNil(t, chunk)
body, err := ioutil.ReadAll(chunk)
require.NoError(t, err)
assert.Equal(t, []byte{2, 1, 0}, body)
err = chunk.Close()
require.NoError(t, err)
}
func TestStore_Prune(t *testing.T) {
store := setupStore(t)
// Pruning too many snapshots should be fine
pruned, err := store.Prune(4)
require.NoError(t, err)
assert.EqualValues(t, 0, pruned)
snapshots, err := store.List()
require.NoError(t, err)
assert.Len(t, snapshots, 4)
// Pruning until the last two heights should leave three snapshots (for two heights)
pruned, err = store.Prune(2)
require.NoError(t, err)
assert.EqualValues(t, 1, pruned)
snapshots, err = store.List()
require.NoError(t, err)
require.Equal(t, []*types.Snapshot{
{Height: 3, Format: 2, Chunks: 3, Hash: hash([][]byte{{3, 2, 0}, {3, 2, 1}, {3, 2, 2}}),
Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{3, 2, 0}, {3, 2, 1}, {3, 2, 2}})},
},
{Height: 2, Format: 2, Chunks: 3, Hash: hash([][]byte{{2, 2, 0}, {2, 2, 1}, {2, 2, 2}}),
Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{2, 2, 0}, {2, 2, 1}, {2, 2, 2}})},
},
{Height: 2, Format: 1, Chunks: 2, Hash: hash([][]byte{{2, 1, 0}, {2, 1, 1}}),
Metadata: types.Metadata{ChunkHashes: checksums([][]byte{{2, 1, 0}, {2, 1, 1}})},
},
}, snapshots)
// Pruning all heights should also be fine
pruned, err = store.Prune(0)
require.NoError(t, err)
assert.EqualValues(t, 3, pruned)
snapshots, err = store.List()
require.NoError(t, err)
assert.Empty(t, snapshots)
}
func TestStore_Save(t *testing.T) {
store := setupStore(t)
// Saving a snapshot should work
snapshot, err := store.Save(4, 1, makeChunks([][]byte{{1}, {2}}))
require.NoError(t, err)
assert.Equal(t, &types.Snapshot{
Height: 4,
Format: 1,
Chunks: 2,
Hash: hash([][]byte{{1}, {2}}),
Metadata: types.Metadata{
ChunkHashes: checksums([][]byte{{1}, {2}}),
},
}, snapshot)
loaded, err := store.Get(snapshot.Height, snapshot.Format)
require.NoError(t, err)
assert.Equal(t, snapshot, loaded)
// Saving an existing snapshot should error
_, err = store.Save(4, 1, makeChunks([][]byte{{1}, {2}}))
require.Error(t, err)
// Saving at height 0 should error
_, err = store.Save(0, 1, makeChunks([][]byte{{1}, {2}}))
require.Error(t, err)
// Saving at format 0 should be fine
_, err = store.Save(1, 0, makeChunks([][]byte{{1}, {2}}))
require.NoError(t, err)
// Saving a snapshot with no chunks should be fine, as should loading it
_, err = store.Save(5, 1, makeChunks([][]byte{}))
require.NoError(t, err)
snapshot, chunks, err := store.Load(5, 1)
require.NoError(t, err)
assert.Equal(t, &types.Snapshot{Height: 5, Format: 1, Hash: hash([][]byte{}), Metadata: types.Metadata{ChunkHashes: [][]byte{}}}, snapshot)
assert.Empty(t, chunks)
// Saving a snapshot should error if a chunk reader returns an error, and it should empty out
// the channel
someErr := errors.New("boom")
pr, pw := io.Pipe()
err = pw.CloseWithError(someErr)
require.NoError(t, err)
ch := make(chan io.ReadCloser, 2)
ch <- pr
ch <- ioutil.NopCloser(bytes.NewBuffer([]byte{0xff}))
close(ch)
_, err = store.Save(6, 1, ch)
require.Error(t, err)
require.True(t, errors.Is(err, someErr))
assert.Empty(t, ch)
// Saving a snapshot should error if a snapshot is already in progress for the same height,
// regardless of format. However, a different height should succeed.
ch = make(chan io.ReadCloser)
go store.Save(7, 1, ch)
time.Sleep(10 * time.Millisecond)
_, err = store.Save(7, 2, makeChunks(nil))
require.Error(t, err)
_, err = store.Save(8, 1, makeChunks(nil))
require.NoError(t, err)
close(ch)
}