1 // Copyright (c) 2013, Suryandaru Triandana <syndtr@gmail.com>
2 // All rights reserved.
4 // Use of this source code is governed by a BSD-style license that can be
5 // found in the LICENSE file.
16 "github.com/syndtr/goleveldb/leveldb/filter"
17 "github.com/syndtr/goleveldb/leveldb/opt"
18 "github.com/syndtr/goleveldb/leveldb/storage"
21 const ctValSize = 1000
23 type dbCorruptHarness struct {
27 func newDbCorruptHarnessWopt(t *testing.T, o *opt.Options) *dbCorruptHarness {
28 h := new(dbCorruptHarness)
33 func newDbCorruptHarness(t *testing.T) *dbCorruptHarness {
34 return newDbCorruptHarnessWopt(t, &opt.Options{
35 BlockCacheCapacity: 100,
36 Strict: opt.StrictJournalChecksum,
40 func (h *dbCorruptHarness) recover() {
45 p.db, err = Recover(h.stor, h.o)
47 t.Fatal("Repair: got error: ", err)
51 func (h *dbCorruptHarness) build(n int) {
57 for i := 0; i < n; i++ {
59 batch.Put(tkey(i), tval(i, ctValSize))
60 err := db.Write(batch, p.wo)
62 t.Fatal("write error: ", err)
67 func (h *dbCorruptHarness) buildShuffled(n int, rnd *rand.Rand) {
73 for i := range rnd.Perm(n) {
75 batch.Put(tkey(i), tval(i, ctValSize))
76 err := db.Write(batch, p.wo)
78 t.Fatal("write error: ", err)
83 func (h *dbCorruptHarness) deleteRand(n, max int, rnd *rand.Rand) {
89 for i := 0; i < n; i++ {
91 batch.Delete(tkey(rnd.Intn(max)))
92 err := db.Write(batch, p.wo)
94 t.Fatal("write error: ", err)
99 func (h *dbCorruptHarness) corrupt(ft storage.FileType, fi, offset, n int) {
103 fds, _ := p.stor.List(ft)
109 t.Fatalf("no such file with type %q with index %d", ft, fi)
113 r, err := h.stor.Open(fd)
115 t.Fatal("cannot open file: ", err)
117 x, err := r.Seek(0, 2)
119 t.Fatal("cannot query file size: ", err)
122 if _, err := r.Seek(0, 0); err != nil {
140 buf := make([]byte, m)
141 _, err = io.ReadFull(r, buf)
143 t.Fatal("cannot read file: ", err)
147 for i := 0; i < n; i++ {
148 buf[offset+i] ^= 0x80
151 err = h.stor.Remove(fd)
153 t.Fatal("cannot remove old file: ", err)
155 w, err := h.stor.Create(fd)
157 t.Fatal("cannot create new file: ", err)
159 _, err = w.Write(buf)
161 t.Fatal("cannot write new file: ", err)
166 func (h *dbCorruptHarness) removeAll(ft storage.FileType) {
167 fds, err := h.stor.List(ft)
169 h.t.Fatal("get files: ", err)
171 for _, fd := range fds {
172 if err := h.stor.Remove(fd); err != nil {
173 h.t.Error("remove file: ", err)
178 func (h *dbCorruptHarness) forceRemoveAll(ft storage.FileType) {
179 fds, err := h.stor.List(ft)
181 h.t.Fatal("get files: ", err)
183 for _, fd := range fds {
184 if err := h.stor.ForceRemove(fd); err != nil {
185 h.t.Error("remove file: ", err)
190 func (h *dbCorruptHarness) removeOne(ft storage.FileType) {
191 fds, err := h.stor.List(ft)
193 h.t.Fatal("get files: ", err)
195 fd := fds[rand.Intn(len(fds))]
196 h.t.Logf("removing file @%d", fd.Num)
197 if err := h.stor.Remove(fd); err != nil {
198 h.t.Error("remove file: ", err)
202 func (h *dbCorruptHarness) check(min, max int) {
207 var n, badk, badv, missed, good int
208 iter := db.NewIterator(nil, p.ro)
211 fmt.Sscanf(string(iter.Key()), "%d", &k)
218 if !bytes.Equal(iter.Value(), tval(k, ctValSize)) {
226 t.Logf("want=%d..%d got=%d badkeys=%d badvalues=%d missed=%d, err=%v",
227 min, max, good, badk, badv, missed, err)
228 if good < min || good > max {
229 t.Errorf("good entries number not in range")
233 func TestCorruptDB_Journal(t *testing.T) {
234 h := newDbCorruptHarness(t)
240 h.corrupt(storage.TypeJournal, -1, 19, 1)
241 h.corrupt(storage.TypeJournal, -1, 32*1024+1000, 1)
247 func TestCorruptDB_Table(t *testing.T) {
248 h := newDbCorruptHarness(t)
253 h.compactRangeAt(0, "", "")
254 h.compactRangeAt(1, "", "")
256 h.corrupt(storage.TypeTable, -1, 100, 1)
262 func TestCorruptDB_TableIndex(t *testing.T) {
263 h := newDbCorruptHarness(t)
269 h.corrupt(storage.TypeTable, -1, -2000, 500)
275 func TestCorruptDB_MissingManifest(t *testing.T) {
276 rnd := rand.New(rand.NewSource(0x0badda7a))
277 h := newDbCorruptHarnessWopt(t, &opt.Options{
278 BlockCacheCapacity: 100,
279 Strict: opt.StrictJournalChecksum,
280 WriteBuffer: 1000 * 60,
286 h.buildShuffled(1000, rnd)
288 h.deleteRand(500, 1000, rnd)
290 h.buildShuffled(1000, rnd)
292 h.deleteRand(500, 1000, rnd)
294 h.buildShuffled(1000, rnd)
298 h.forceRemoveAll(storage.TypeManifest)
305 h.compactRange("", "")
312 func TestCorruptDB_SequenceNumberRecovery(t *testing.T) {
313 h := newDbCorruptHarness(t)
324 h.getVal("foo", "v5")
326 h.getVal("foo", "v6")
329 h.getVal("foo", "v6")
332 func TestCorruptDB_SequenceNumberRecoveryTable(t *testing.T) {
333 h := newDbCorruptHarness(t)
346 h.getVal("foo", "v5")
348 h.getVal("foo", "v6")
351 h.getVal("foo", "v6")
354 func TestCorruptDB_CorruptedManifest(t *testing.T) {
355 h := newDbCorruptHarness(t)
358 h.put("foo", "hello")
360 h.compactRange("", "")
362 h.corrupt(storage.TypeManifest, -1, 0, 1000)
366 h.getVal("foo", "hello")
369 func TestCorruptDB_CompactionInputError(t *testing.T) {
370 h := newDbCorruptHarness(t)
376 h.corrupt(storage.TypeTable, -1, 100, 1)
382 h.check(10000, 10000)
385 func TestCorruptDB_UnrelatedKeys(t *testing.T) {
386 h := newDbCorruptHarness(t)
392 h.corrupt(storage.TypeTable, -1, 100, 1)
395 h.put(string(tkey(1000)), string(tval(1000, ctValSize)))
396 h.getVal(string(tkey(1000)), string(tval(1000, ctValSize)))
398 h.getVal(string(tkey(1000)), string(tval(1000, ctValSize)))
401 func TestCorruptDB_Level0NewerFileHasOlderSeqnum(t *testing.T) {
402 h := newDbCorruptHarness(t)
417 h.compactRangeAt(1, "", "")
427 func TestCorruptDB_RecoverInvalidSeq_Issue53(t *testing.T) {
428 h := newDbCorruptHarness(t)
443 h.compactRangeAt(0, "", "")
453 func TestCorruptDB_MissingTableFiles(t *testing.T) {
454 h := newDbCorruptHarness(t)
467 h.removeOne(storage.TypeTable)
471 func TestCorruptDB_RecoverTable(t *testing.T) {
472 h := newDbCorruptHarnessWopt(t, &opt.Options{
473 WriteBuffer: 112 * opt.KiB,
474 CompactionTableSize: 90 * opt.KiB,
475 Filter: filter.NewBloomFilter(10),
481 h.compactRangeAt(0, "", "")
482 h.compactRangeAt(1, "", "")
485 h.corrupt(storage.TypeTable, 0, 1000, 1)
486 h.corrupt(storage.TypeTable, 3, 10000, 1)
487 // Corrupted filter shouldn't affect recovery.
488 h.corrupt(storage.TypeTable, 3, 113888, 10)
489 h.corrupt(storage.TypeTable, -1, 20000, 1)
493 t.Errorf("invalid seq, want=%d got=%d", seq, h.db.seq)