1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package testutil
16
17 import (
18 "context"
19 "errors"
20 "fmt"
21 "os"
22 "testing"
23
24 "github.com/stretchr/testify/assert"
25 "go.etcd.io/bbolt"
26 "go.etcd.io/etcd/api/v3/mvccpb"
27 )
28
29 const (
30
31 CompactionCycle = 71
32 )
33
34 func TestCompactionHash(ctx context.Context, t *testing.T, h CompactionHashTestCase, compactionBatchLimit int) {
35 var totalRevisions int64 = 1210
36 assert.Less(t, int64(compactionBatchLimit), totalRevisions)
37 assert.Less(t, int64(CompactionCycle*10), totalRevisions)
38 var rev int64
39 for ; rev < totalRevisions; rev += CompactionCycle {
40 testCompactionHash(ctx, t, h, rev, rev+CompactionCycle)
41 }
42 testCompactionHash(ctx, t, h, rev, rev+totalRevisions)
43 }
44
45 type CompactionHashTestCase interface {
46 Put(ctx context.Context, key, value string) error
47 Delete(ctx context.Context, key string) error
48 HashByRev(ctx context.Context, rev int64) (KeyValueHash, error)
49 Defrag(ctx context.Context) error
50 Compact(ctx context.Context, rev int64) error
51 }
52
53 type KeyValueHash struct {
54 Hash uint32
55 CompactRevision int64
56 Revision int64
57 }
58
59 func testCompactionHash(ctx context.Context, t *testing.T, h CompactionHashTestCase, start, stop int64) {
60 for i := start; i <= stop; i++ {
61 if i%67 == 0 {
62 err := h.Delete(ctx, PickKey(i+83))
63 assert.NoError(t, err, "error on delete")
64 } else {
65 err := h.Put(ctx, PickKey(i), fmt.Sprint(i))
66 assert.NoError(t, err, "error on put")
67 }
68 }
69 hash1, err := h.HashByRev(ctx, stop)
70 assert.NoError(t, err, "error on hash (rev %v)", stop)
71
72 err = h.Compact(ctx, stop)
73 assert.NoError(t, err, "error on compact (rev %v)", stop)
74
75 err = h.Defrag(ctx)
76 assert.NoError(t, err, "error on defrag")
77
78 hash2, err := h.HashByRev(ctx, stop)
79 assert.NoError(t, err, "error on hash (rev %v)", stop)
80 assert.Equal(t, hash1, hash2, "hashes do not match on rev %v", stop)
81 }
82
83 func PickKey(i int64) string {
84 if i%(CompactionCycle*2) == 30 {
85 return "zenek"
86 }
87 if i%CompactionCycle == 30 {
88 return "xavery"
89 }
90
91 switch i % 7 {
92 case 0:
93 return "alice"
94 case 1:
95 return "bob"
96 case 2:
97 return "celine"
98 case 3:
99 return "dominik"
100 case 4:
101 return "eve"
102 case 5:
103 return "frederica"
104 case 6:
105 return "gorge"
106 default:
107 panic("Can't count")
108 }
109 }
110
111 func CorruptBBolt(fpath string) error {
112 db, derr := bbolt.Open(fpath, os.ModePerm, &bbolt.Options{})
113 if derr != nil {
114 return derr
115 }
116 defer db.Close()
117
118 return db.Update(func(tx *bbolt.Tx) error {
119 b := tx.Bucket([]byte("key"))
120 if b == nil {
121 return errors.New("got nil bucket for 'key'")
122 }
123 keys, vals := [][]byte{}, [][]byte{}
124 c := b.Cursor()
125 for k, v := c.First(); k != nil; k, v = c.Next() {
126 keys = append(keys, k)
127 var kv mvccpb.KeyValue
128 if uerr := kv.Unmarshal(v); uerr != nil {
129 return uerr
130 }
131 kv.Key[0]++
132 kv.Value[0]++
133 v2, v2err := kv.Marshal()
134 if v2err != nil {
135 return v2err
136 }
137 vals = append(vals, v2)
138 }
139 for i := range keys {
140 if perr := b.Put(keys[i], vals[i]); perr != nil {
141 return perr
142 }
143 }
144 return nil
145 })
146 }
147
View as plain text