mvcc/backend tests: Refactor: Do not mix testing&prod code.

This commit is contained in:
Piotr Tabor
2021-04-09 15:49:50 +02:00
parent ea287dd9f8
commit d7d110b5a8
20 changed files with 257 additions and 196 deletions

View File

@@ -23,7 +23,6 @@ import (
"path/filepath"
"sync"
"sync/atomic"
"testing"
"time"
humanize "github.com/dustin/go-humanize"
@@ -544,22 +543,6 @@ func (b *backend) OpenReadTxN() int64 {
return atomic.LoadInt64(&b.openReadTxN)
}
// NewTmpBackend creates a backend implementation for testing.
func NewTmpBackend(t testing.TB, batchInterval time.Duration, batchLimit int) (*backend, string) {
dir, err := ioutil.TempDir(t.TempDir(), "etcd_backend_test")
if err != nil {
panic(err)
}
tmpPath := filepath.Join(dir, "database")
bcfg := DefaultBackendConfig()
bcfg.Path, bcfg.BatchInterval, bcfg.BatchLimit = tmpPath, batchInterval, batchLimit
return newBackend(bcfg), tmpPath
}
func NewDefaultTmpBackend(t testing.TB) (*backend, string) {
return NewTmpBackend(t, defaultBatchInterval, defaultBatchLimit)
}
type snapshot struct {
*bolt.Tx
stopc chan struct{}

View File

@@ -12,28 +12,31 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package backend
package backend_test
import (
"crypto/rand"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
betesting "go.etcd.io/etcd/server/v3/mvcc/backend/testing"
)
func BenchmarkBackendPut(b *testing.B) {
backend, tmppath := NewTmpBackend(b, 100*time.Millisecond, 10000)
defer backend.Close()
defer os.Remove(tmppath)
backend, _ := betesting.NewTmpBackend(b, 100*time.Millisecond, 10000)
defer betesting.Close(b, backend)
// prepare keys
keys := make([][]byte, b.N)
for i := 0; i < b.N; i++ {
keys[i] = make([]byte, 64)
rand.Read(keys[i])
_, err := rand.Read(keys[i])
assert.NoError(b, err)
}
value := make([]byte, 128)
rand.Read(value)
_, err := rand.Read(value)
assert.NoError(b, err)
batchTx := backend.BatchTx()

View File

@@ -12,22 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package backend
package backend_test
import (
"fmt"
"io/ioutil"
"os"
"reflect"
"testing"
"time"
"github.com/stretchr/testify/assert"
bolt "go.etcd.io/bbolt"
"go.etcd.io/etcd/server/v3/mvcc/backend"
betesting "go.etcd.io/etcd/server/v3/mvcc/backend/testing"
)
func TestBackendClose(t *testing.T) {
b, tmpPath := NewTmpBackend(t, time.Hour, 10000)
defer os.Remove(tmpPath)
b, _ := betesting.NewTmpBackend(t, time.Hour, 10000)
// check close could work
done := make(chan struct{})
@@ -46,8 +47,8 @@ func TestBackendClose(t *testing.T) {
}
func TestBackendSnapshot(t *testing.T) {
b, tmpPath := NewTmpBackend(t, time.Hour, 10000)
defer cleanup(b, tmpPath)
b, _ := betesting.NewTmpBackend(t, time.Hour, 10000)
defer betesting.Close(t, b)
tx := b.BatchTx()
tx.Lock()
@@ -57,22 +58,22 @@ func TestBackendSnapshot(t *testing.T) {
b.ForceCommit()
// write snapshot to a new file
f, err := ioutil.TempFile(os.TempDir(), "etcd_backend_test")
f, err := ioutil.TempFile(t.TempDir(), "etcd_backend_test")
if err != nil {
t.Fatal(err)
}
snap := b.Snapshot()
defer snap.Close()
defer func() { assert.NoError(t, snap.Close()) }()
if _, err := snap.WriteTo(f); err != nil {
t.Fatal(err)
}
f.Close()
assert.NoError(t, f.Close())
// bootstrap new backend from the snapshot
bcfg := DefaultBackendConfig()
bcfg := backend.DefaultBackendConfig()
bcfg.Path, bcfg.BatchInterval, bcfg.BatchLimit = f.Name(), time.Hour, 10000
nb := New(bcfg)
defer cleanup(nb, f.Name())
nb := backend.New(bcfg)
defer betesting.Close(t, nb)
newTx := nb.BatchTx()
newTx.Lock()
@@ -86,10 +87,10 @@ func TestBackendSnapshot(t *testing.T) {
func TestBackendBatchIntervalCommit(t *testing.T) {
// start backend with super short batch interval so
// we do not need to wait long before commit to happen.
b, tmpPath := NewTmpBackend(t, time.Nanosecond, 10000)
defer cleanup(b, tmpPath)
b, _ := betesting.NewTmpBackend(t, time.Nanosecond, 10000)
defer betesting.Close(t, b)
pc := b.Commits()
pc := backend.CommitsForTest(b)
tx := b.BatchTx()
tx.Lock()
@@ -98,14 +99,14 @@ func TestBackendBatchIntervalCommit(t *testing.T) {
tx.Unlock()
for i := 0; i < 10; i++ {
if b.Commits() >= pc+1 {
if backend.CommitsForTest(b) >= pc+1 {
break
}
time.Sleep(time.Duration(i*100) * time.Millisecond)
}
// check whether put happens via db view
b.db.View(func(tx *bolt.Tx) error {
assert.NoError(t, backend.DbFromBackendForTest(b).View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte("test"))
if bucket == nil {
t.Errorf("bucket test does not exit")
@@ -116,17 +117,17 @@ func TestBackendBatchIntervalCommit(t *testing.T) {
t.Errorf("foo key failed to written in backend")
}
return nil
})
}))
}
func TestBackendDefrag(t *testing.T) {
b, tmpPath := NewDefaultTmpBackend(t)
defer cleanup(b, tmpPath)
b, _ := betesting.NewDefaultTmpBackend(t)
defer betesting.Close(t, b)
tx := b.BatchTx()
tx.Lock()
tx.UnsafeCreateBucket([]byte("test"))
for i := 0; i < defragLimit+100; i++ {
for i := 0; i < backend.DefragLimitForTest()+100; i++ {
tx.UnsafePut([]byte("test"), []byte(fmt.Sprintf("foo_%d", i)), []byte("bar"))
}
tx.Unlock()
@@ -178,8 +179,8 @@ func TestBackendDefrag(t *testing.T) {
// TestBackendWriteback ensures writes are stored to the read txn on write txn unlock.
func TestBackendWriteback(t *testing.T) {
b, tmpPath := NewDefaultTmpBackend(t)
defer cleanup(b, tmpPath)
b, _ := betesting.NewDefaultTmpBackend(t)
defer betesting.Close(t, b)
tx := b.BatchTx()
tx.Lock()
@@ -252,8 +253,8 @@ func TestBackendWriteback(t *testing.T) {
// TestConcurrentReadTx ensures that current read transaction can see all prior writes stored in read buffer
func TestConcurrentReadTx(t *testing.T) {
b, tmpPath := NewTmpBackend(t, time.Hour, 10000)
defer cleanup(b, tmpPath)
b, _ := betesting.NewTmpBackend(t, time.Hour, 10000)
defer betesting.Close(t, b)
wtx1 := b.BatchTx()
wtx1.Lock()
@@ -282,8 +283,8 @@ func TestConcurrentReadTx(t *testing.T) {
// TestBackendWritebackForEach checks that partially written / buffered
// data is visited in the same order as fully committed data.
func TestBackendWritebackForEach(t *testing.T) {
b, tmpPath := NewTmpBackend(t, time.Hour, 10000)
defer cleanup(b, tmpPath)
b, _ := betesting.NewTmpBackend(t, time.Hour, 10000)
defer betesting.Close(t, b)
tx := b.BatchTx()
tx.Lock()
@@ -312,7 +313,7 @@ func TestBackendWritebackForEach(t *testing.T) {
}
rtx := b.ReadTx()
rtx.RLock()
rtx.UnsafeForEach([]byte("key"), getSeq)
assert.NoError(t, rtx.UnsafeForEach([]byte("key"), getSeq))
rtx.RUnlock()
partialSeq := seq
@@ -321,15 +322,10 @@ func TestBackendWritebackForEach(t *testing.T) {
b.ForceCommit()
tx.Lock()
tx.UnsafeForEach([]byte("key"), getSeq)
assert.NoError(t, tx.UnsafeForEach([]byte("key"), getSeq))
tx.Unlock()
if seq != partialSeq {
t.Fatalf("expected %q, got %q", seq, partialSeq)
}
}
func cleanup(b Backend, path string) {
b.Close()
os.Remove(path)
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package backend
package backend_test
import (
"reflect"
@@ -20,15 +20,17 @@ import (
"time"
bolt "go.etcd.io/bbolt"
"go.etcd.io/etcd/server/v3/mvcc/backend"
betesting "go.etcd.io/etcd/server/v3/mvcc/backend/testing"
)
func TestBatchTxPut(t *testing.T) {
b, tmpPath := NewTmpBackend(t, time.Hour, 10000)
defer cleanup(b, tmpPath)
b, _ := betesting.NewTmpBackend(t, time.Hour, 10000)
defer betesting.Close(t, b)
tx := b.BatchTx()
tx := b.batchTx
tx.Lock()
defer tx.Unlock()
// create bucket
tx.UnsafeCreateBucket([]byte("test"))
@@ -37,21 +39,25 @@ func TestBatchTxPut(t *testing.T) {
v := []byte("bar")
tx.UnsafePut([]byte("test"), []byte("foo"), v)
tx.Unlock()
// check put result before and after tx is committed
for k := 0; k < 2; k++ {
tx.Lock()
_, gv := tx.UnsafeRange([]byte("test"), []byte("foo"), nil, 0)
tx.Unlock()
if !reflect.DeepEqual(gv[0], v) {
t.Errorf("v = %s, want %s", string(gv[0]), string(v))
}
tx.commit(false)
tx.Commit()
}
}
func TestBatchTxRange(t *testing.T) {
b, tmpPath := NewTmpBackend(t, time.Hour, 10000)
defer cleanup(b, tmpPath)
b, _ := betesting.NewTmpBackend(t, time.Hour, 10000)
defer betesting.Close(t, b)
tx := b.batchTx
tx := b.BatchTx()
tx.Lock()
defer tx.Unlock()
@@ -119,33 +125,36 @@ func TestBatchTxRange(t *testing.T) {
}
func TestBatchTxDelete(t *testing.T) {
b, tmpPath := NewTmpBackend(t, time.Hour, 10000)
defer cleanup(b, tmpPath)
b, _ := betesting.NewTmpBackend(t, time.Hour, 10000)
defer betesting.Close(t, b)
tx := b.batchTx
tx := b.BatchTx()
tx.Lock()
defer tx.Unlock()
tx.UnsafeCreateBucket([]byte("test"))
tx.UnsafePut([]byte("test"), []byte("foo"), []byte("bar"))
tx.UnsafeDelete([]byte("test"), []byte("foo"))
tx.Unlock()
// check put result before and after tx is committed
for k := 0; k < 2; k++ {
tx.Lock()
ks, _ := tx.UnsafeRange([]byte("test"), []byte("foo"), nil, 0)
tx.Unlock()
if len(ks) != 0 {
t.Errorf("keys on foo = %v, want nil", ks)
}
tx.commit(false)
tx.Commit()
}
}
func TestBatchTxCommit(t *testing.T) {
b, tmpPath := NewTmpBackend(t, time.Hour, 10000)
defer cleanup(b, tmpPath)
b, _ := betesting.NewTmpBackend(t, time.Hour, 10000)
defer betesting.Close(t, b)
tx := b.batchTx
tx := b.BatchTx()
tx.Lock()
tx.UnsafeCreateBucket([]byte("test"))
tx.UnsafePut([]byte("test"), []byte("foo"), []byte("bar"))
@@ -154,7 +163,7 @@ func TestBatchTxCommit(t *testing.T) {
tx.Commit()
// check whether put happens via db view
b.db.View(func(tx *bolt.Tx) error {
backend.DbFromBackendForTest(b).View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte("test"))
if bucket == nil {
t.Errorf("bucket test does not exit")
@@ -171,10 +180,10 @@ func TestBatchTxCommit(t *testing.T) {
func TestBatchTxBatchLimitCommit(t *testing.T) {
// start backend with batch limit 1 so one write can
// trigger a commit
b, tmpPath := NewTmpBackend(t, time.Hour, 1)
defer cleanup(b, tmpPath)
b, _ := betesting.NewTmpBackend(t, time.Hour, 1)
defer betesting.Close(t, b)
tx := b.batchTx
tx := b.BatchTx()
tx.Lock()
tx.UnsafeCreateBucket([]byte("test"))
tx.UnsafePut([]byte("test"), []byte("foo"), []byte("bar"))
@@ -182,7 +191,7 @@ func TestBatchTxBatchLimitCommit(t *testing.T) {
// batch limit commit should have been triggered
// check whether put happens via db view
b.db.View(func(tx *bolt.Tx) error {
backend.DbFromBackendForTest(b).View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte("test"))
if bucket == nil {
t.Errorf("bucket test does not exit")

View File

@@ -0,0 +1,15 @@
package backend
import bolt "go.etcd.io/bbolt"
func DbFromBackendForTest(b Backend) *bolt.DB {
return b.(*backend).db
}
func DefragLimitForTest() int {
return defragLimit
}
func CommitsForTest(b Backend) int64 {
return b.(*backend).Commits()
}

View File

@@ -0,0 +1,52 @@
// Copyright 2021 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package betesting
import (
"io/ioutil"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.etcd.io/etcd/server/v3/mvcc/backend"
"go.uber.org/zap/zaptest"
)
func NewTmpBackendFromCfg(t testing.TB, bcfg backend.BackendConfig) (backend.Backend, string) {
dir, err := ioutil.TempDir(t.TempDir(), "etcd_backend_test")
if err != nil {
panic(err)
}
tmpPath := filepath.Join(dir, "database")
bcfg.Path = tmpPath
bcfg.Logger = zaptest.NewLogger(t)
return backend.New(bcfg), tmpPath
}
// NewTmpBackend creates a backend implementation for testing.
func NewTmpBackend(t testing.TB, batchInterval time.Duration, batchLimit int) (backend.Backend, string) {
bcfg := backend.DefaultBackendConfig()
bcfg.BatchInterval, bcfg.BatchLimit = batchInterval, batchLimit
return NewTmpBackendFromCfg(t, bcfg)
}
func NewDefaultTmpBackend(t testing.TB) (backend.Backend, string) {
return NewTmpBackendFromCfg(t, backend.DefaultBackendConfig())
}
func Close(t testing.TB, b backend.Backend) {
assert.NoError(t, b.Close())
}