Files
etcd/server/storage/mvcc/key_index_test.go
Wei Fu d8b4192574 mvcc: restore tombstone index if it's first revision
The tombstone could be the only one available revision in database.
It happens when all historical revisions have been deleted in previous
compactions. Since tombstone revision is still in database, we should
restore it as valid key index. Otherwise, we lost that event.

Signed-off-by: Wei Fu <fuweid89@gmail.com>
2025-01-19 18:12:25 -05:00

798 lines
20 KiB
Go

// Copyright 2015 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 mvcc
import (
"errors"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest"
)
func TestRestoreTombstone(t *testing.T) {
lg := zaptest.NewLogger(t)
// restore from tombstone
//
// key: "foo"
// modified: 16
// "created": 16
// generations:
// {empty}
// {{16, 0}(t)[0]}
//
ki := &keyIndex{key: []byte("foo")}
ki.restoreTombstone(lg, 16, 0)
// get should return not found
for retAt := 16; retAt <= 20; retAt++ {
_, _, _, err := ki.get(lg, int64(retAt))
require.ErrorIs(t, err, ErrRevisionNotFound)
}
// doCompact should keep that tombstone
availables := map[Revision]struct{}{}
ki.doCompact(16, availables)
require.Len(t, availables, 1)
_, ok := availables[Revision{Main: 16}]
require.True(t, ok)
// should be able to put new revisions
ki.put(lg, 17, 0)
ki.put(lg, 18, 0)
revs := ki.since(lg, 16)
require.Equal(t, []Revision{{16, 0}, {17, 0}, {18, 0}}, revs)
// compaction should remove restored tombstone
ki.compact(lg, 17, map[Revision]struct{}{})
require.Len(t, ki.generations, 1)
require.Equal(t, []Revision{{17, 0}, {18, 0}}, ki.generations[0].revs)
}
func TestKeyIndexGet(t *testing.T) {
// key: "foo"
// modified: 16
// generations:
// {empty}
// {{14, 0}[1], {15, 1}[2], {16, 0}(t)[3]}
// {{8, 0}[1], {10, 0}[2], {12, 0}(t)[3]}
// {{2, 0}[1], {4, 0}[2], {6, 0}(t)[3]}
ki := newTestKeyIndex(zaptest.NewLogger(t))
ki.compact(zaptest.NewLogger(t), 4, make(map[Revision]struct{}))
tests := []struct {
rev int64
wmod Revision
wcreat Revision
wver int64
werr error
}{
{17, Revision{}, Revision{}, 0, ErrRevisionNotFound},
{16, Revision{}, Revision{}, 0, ErrRevisionNotFound},
// get on generation 3
{15, Revision{Main: 15, Sub: 1}, Revision{Main: 14}, 2, nil},
{14, Revision{Main: 14}, Revision{Main: 14}, 1, nil},
{13, Revision{}, Revision{}, 0, ErrRevisionNotFound},
{12, Revision{}, Revision{}, 0, ErrRevisionNotFound},
// get on generation 2
{11, Revision{Main: 10}, Revision{Main: 8}, 2, nil},
{10, Revision{Main: 10}, Revision{Main: 8}, 2, nil},
{9, Revision{Main: 8}, Revision{Main: 8}, 1, nil},
{8, Revision{Main: 8}, Revision{Main: 8}, 1, nil},
{7, Revision{}, Revision{}, 0, ErrRevisionNotFound},
{6, Revision{}, Revision{}, 0, ErrRevisionNotFound},
// get on generation 1
{5, Revision{Main: 4}, Revision{Main: 2}, 2, nil},
{4, Revision{Main: 4}, Revision{Main: 2}, 2, nil},
{3, Revision{}, Revision{}, 0, ErrRevisionNotFound},
{2, Revision{}, Revision{}, 0, ErrRevisionNotFound},
{1, Revision{}, Revision{}, 0, ErrRevisionNotFound},
{0, Revision{}, Revision{}, 0, ErrRevisionNotFound},
}
for i, tt := range tests {
mod, creat, ver, err := ki.get(zaptest.NewLogger(t), tt.rev)
if !errors.Is(err, tt.werr) {
t.Errorf("#%d: err = %v, want %v", i, err, tt.werr)
}
if mod != tt.wmod {
t.Errorf("#%d: modified = %+v, want %+v", i, mod, tt.wmod)
}
if creat != tt.wcreat {
t.Errorf("#%d: created = %+v, want %+v", i, creat, tt.wcreat)
}
if ver != tt.wver {
t.Errorf("#%d: version = %d, want %d", i, ver, tt.wver)
}
}
}
func TestKeyIndexSince(t *testing.T) {
ki := newTestKeyIndex(zaptest.NewLogger(t))
ki.compact(zaptest.NewLogger(t), 4, make(map[Revision]struct{}))
allRevs := []Revision{
{Main: 4},
{Main: 6},
{Main: 8},
{Main: 10},
{Main: 12},
{Main: 14},
{Main: 15, Sub: 1},
{Main: 16},
}
tests := []struct {
rev int64
wrevs []Revision
}{
{17, nil},
{16, allRevs[7:]},
{15, allRevs[6:]},
{14, allRevs[5:]},
{13, allRevs[5:]},
{12, allRevs[4:]},
{11, allRevs[4:]},
{10, allRevs[3:]},
{9, allRevs[3:]},
{8, allRevs[2:]},
{7, allRevs[2:]},
{6, allRevs[1:]},
{5, allRevs[1:]},
{4, allRevs},
{3, allRevs},
{2, allRevs},
{1, allRevs},
{0, allRevs},
}
for i, tt := range tests {
revs := ki.since(zaptest.NewLogger(t), tt.rev)
if !reflect.DeepEqual(revs, tt.wrevs) {
t.Errorf("#%d: revs = %+v, want %+v", i, revs, tt.wrevs)
}
}
}
func TestKeyIndexPut(t *testing.T) {
ki := &keyIndex{key: []byte("foo")}
ki.put(zaptest.NewLogger(t), 5, 0)
wki := &keyIndex{
key: []byte("foo"),
modified: Revision{Main: 5},
generations: []generation{{created: Revision{Main: 5}, ver: 1, revs: []Revision{{Main: 5}}}},
}
if !reflect.DeepEqual(ki, wki) {
t.Errorf("ki = %+v, want %+v", ki, wki)
}
ki.put(zaptest.NewLogger(t), 7, 0)
wki = &keyIndex{
key: []byte("foo"),
modified: Revision{Main: 7},
generations: []generation{{created: Revision{Main: 5}, ver: 2, revs: []Revision{{Main: 5}, {Main: 7}}}},
}
if !reflect.DeepEqual(ki, wki) {
t.Errorf("ki = %+v, want %+v", ki, wki)
}
}
func TestKeyIndexRestore(t *testing.T) {
ki := &keyIndex{key: []byte("foo")}
ki.restore(zaptest.NewLogger(t), Revision{Main: 5}, Revision{Main: 7}, 2)
wki := &keyIndex{
key: []byte("foo"),
modified: Revision{Main: 7},
generations: []generation{{created: Revision{Main: 5}, ver: 2, revs: []Revision{{Main: 7}}}},
}
if !reflect.DeepEqual(ki, wki) {
t.Errorf("ki = %+v, want %+v", ki, wki)
}
}
func TestKeyIndexTombstone(t *testing.T) {
ki := &keyIndex{key: []byte("foo")}
ki.put(zaptest.NewLogger(t), 5, 0)
err := ki.tombstone(zaptest.NewLogger(t), 7, 0)
if err != nil {
t.Errorf("unexpected tombstone error: %v", err)
}
wki := &keyIndex{
key: []byte("foo"),
modified: Revision{Main: 7},
generations: []generation{{created: Revision{Main: 5}, ver: 2, revs: []Revision{{Main: 5}, {Main: 7}}}, {}},
}
if !reflect.DeepEqual(ki, wki) {
t.Errorf("ki = %+v, want %+v", ki, wki)
}
ki.put(zaptest.NewLogger(t), 8, 0)
ki.put(zaptest.NewLogger(t), 9, 0)
err = ki.tombstone(zaptest.NewLogger(t), 15, 0)
if err != nil {
t.Errorf("unexpected tombstone error: %v", err)
}
wki = &keyIndex{
key: []byte("foo"),
modified: Revision{Main: 15},
generations: []generation{
{created: Revision{Main: 5}, ver: 2, revs: []Revision{{Main: 5}, {Main: 7}}},
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 8}, {Main: 9}, {Main: 15}}},
{},
},
}
if !reflect.DeepEqual(ki, wki) {
t.Errorf("ki = %+v, want %+v", ki, wki)
}
err = ki.tombstone(zaptest.NewLogger(t), 16, 0)
if !errors.Is(err, ErrRevisionNotFound) {
t.Errorf("tombstone error = %v, want %v", err, ErrRevisionNotFound)
}
}
func TestKeyIndexCompactAndKeep(t *testing.T) {
tests := []struct {
compact int64
wki *keyIndex
wam map[Revision]struct{}
}{
{
1,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 2}, ver: 3, revs: []Revision{{Main: 2}, {Main: 4}, {Main: 6}}},
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 8}, {Main: 10}, {Main: 12}}},
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{},
},
{
2,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 2}, ver: 3, revs: []Revision{{Main: 2}, {Main: 4}, {Main: 6}}},
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 8}, {Main: 10}, {Main: 12}}},
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 2}: {},
},
},
{
3,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 2}, ver: 3, revs: []Revision{{Main: 2}, {Main: 4}, {Main: 6}}},
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 8}, {Main: 10}, {Main: 12}}},
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 2}: {},
},
},
{
4,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 2}, ver: 3, revs: []Revision{{Main: 4}, {Main: 6}}},
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 8}, {Main: 10}, {Main: 12}}},
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 4}: {},
},
},
{
5,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 2}, ver: 3, revs: []Revision{{Main: 4}, {Main: 6}}},
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 8}, {Main: 10}, {Main: 12}}},
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 4}: {},
},
},
{
6,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 2}, ver: 3, revs: []Revision{{Main: 6}}},
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 8}, {Main: 10}, {Main: 12}}},
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 6}: {},
},
},
{
7,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 8}, {Main: 10}, {Main: 12}}},
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{},
},
{
8,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 8}, {Main: 10}, {Main: 12}}},
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 8}: {},
},
},
{
9,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 8}, {Main: 10}, {Main: 12}}},
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 8}: {},
},
},
{
10,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 10}, {Main: 12}}},
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 10}: {},
},
},
{
11,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 10}, {Main: 12}}},
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 10}: {},
},
},
{
12,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 8}, ver: 3, revs: []Revision{{Main: 12}}},
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 12}: {},
},
},
{
13,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{},
},
{
14,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 14}, {Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 14}: {},
},
},
{
15,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 15, Sub: 1}, {Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 15, Sub: 1}: {},
},
},
{
16,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{created: Revision{Main: 14}, ver: 3, revs: []Revision{{Main: 16}}},
{},
},
},
map[Revision]struct{}{
{Main: 16}: {},
},
},
{
17,
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 16},
generations: []generation{
{},
},
},
map[Revision]struct{}{},
},
}
isTombstoneRevFn := func(ki *keyIndex, rev int64) bool {
for i := 0; i < len(ki.generations)-1; i++ {
g := ki.generations[i]
if l := len(g.revs); l > 0 && g.revs[l-1].Main == rev {
return true
}
}
return false
}
// Continuous Compaction and finding Keep
ki := newTestKeyIndex(zaptest.NewLogger(t))
for i, tt := range tests {
isTombstone := isTombstoneRevFn(ki, tt.compact)
am := make(map[Revision]struct{})
kiclone := cloneKeyIndex(ki)
ki.keep(tt.compact, am)
if !reflect.DeepEqual(ki, kiclone) {
t.Errorf("#%d: ki = %+v, want %+v", i, ki, kiclone)
}
if isTombstone {
assert.Emptyf(t, am, "#%d: ki = %d, keep result wants empty because tombstone", i, ki)
} else {
assert.Equalf(t, tt.wam, am,
"#%d: ki = %d, compact keep should be equal to keep keep if it's not tombstone", i, ki)
}
am = make(map[Revision]struct{})
ki.compact(zaptest.NewLogger(t), tt.compact, am)
if !reflect.DeepEqual(ki, tt.wki) {
t.Errorf("#%d: ki = %+v, want %+v", i, ki, tt.wki)
}
if !reflect.DeepEqual(am, tt.wam) {
t.Errorf("#%d: am = %+v, want %+v", i, am, tt.wam)
}
}
// Jump Compaction and finding Keep
ki = newTestKeyIndex(zaptest.NewLogger(t))
for i, tt := range tests {
if !isTombstoneRevFn(ki, tt.compact) {
am := make(map[Revision]struct{})
kiclone := cloneKeyIndex(ki)
ki.keep(tt.compact, am)
if !reflect.DeepEqual(ki, kiclone) {
t.Errorf("#%d: ki = %+v, want %+v", i, ki, kiclone)
}
if !reflect.DeepEqual(am, tt.wam) {
t.Errorf("#%d: am = %+v, want %+v", i, am, tt.wam)
}
am = make(map[Revision]struct{})
ki.compact(zaptest.NewLogger(t), tt.compact, am)
if !reflect.DeepEqual(ki, tt.wki) {
t.Errorf("#%d: ki = %+v, want %+v", i, ki, tt.wki)
}
if !reflect.DeepEqual(am, tt.wam) {
t.Errorf("#%d: am = %+v, want %+v", i, am, tt.wam)
}
}
}
kiClone := newTestKeyIndex(zaptest.NewLogger(t))
// Once Compaction and finding Keep
for i, tt := range tests {
ki := newTestKeyIndex(zaptest.NewLogger(t))
am := make(map[Revision]struct{})
ki.keep(tt.compact, am)
if !reflect.DeepEqual(ki, kiClone) {
t.Errorf("#%d: ki = %+v, want %+v", i, ki, kiClone)
}
if isTombstoneRevFn(ki, tt.compact) {
assert.Emptyf(t, am, "#%d: ki = %d, keep result wants empty because tombstone", i, ki)
} else {
assert.Equalf(t, tt.wam, am,
"#%d: ki = %d, compact keep should be equal to keep keep if it's not tombstone", i, ki)
}
am = make(map[Revision]struct{})
ki.compact(zaptest.NewLogger(t), tt.compact, am)
if !reflect.DeepEqual(ki, tt.wki) {
t.Errorf("#%d: ki = %+v, want %+v", i, ki, tt.wki)
}
if !reflect.DeepEqual(am, tt.wam) {
t.Errorf("#%d: am = %+v, want %+v", i, am, tt.wam)
}
}
}
func cloneKeyIndex(ki *keyIndex) *keyIndex {
generations := make([]generation, len(ki.generations))
for i, gen := range ki.generations {
generations[i] = *cloneGeneration(&gen)
}
return &keyIndex{ki.key, ki.modified, generations}
}
func cloneGeneration(g *generation) *generation {
if g.revs == nil {
return &generation{g.ver, g.created, nil}
}
tmp := make([]Revision, len(g.revs))
copy(tmp, g.revs)
return &generation{g.ver, g.created, tmp}
}
// TestKeyIndexCompactOnFurtherRev tests that compact on version that
// higher than last modified version works well
func TestKeyIndexCompactOnFurtherRev(t *testing.T) {
ki := &keyIndex{key: []byte("foo")}
ki.put(zaptest.NewLogger(t), 1, 0)
ki.put(zaptest.NewLogger(t), 2, 0)
am := make(map[Revision]struct{})
ki.compact(zaptest.NewLogger(t), 3, am)
wki := &keyIndex{
key: []byte("foo"),
modified: Revision{Main: 2},
generations: []generation{
{created: Revision{Main: 1}, ver: 2, revs: []Revision{{Main: 2}}},
},
}
wam := map[Revision]struct{}{
{Main: 2}: {},
}
if !reflect.DeepEqual(ki, wki) {
t.Errorf("ki = %+v, want %+v", ki, wki)
}
if !reflect.DeepEqual(am, wam) {
t.Errorf("am = %+v, want %+v", am, wam)
}
}
func TestKeyIndexIsEmpty(t *testing.T) {
tests := []struct {
ki *keyIndex
w bool
}{
{
&keyIndex{
key: []byte("foo"),
generations: []generation{{}},
},
true,
},
{
&keyIndex{
key: []byte("foo"),
modified: Revision{Main: 2},
generations: []generation{
{created: Revision{Main: 1}, ver: 2, revs: []Revision{{Main: 2}}},
},
},
false,
},
}
for i, tt := range tests {
g := tt.ki.isEmpty()
if g != tt.w {
t.Errorf("#%d: isEmpty = %v, want %v", i, g, tt.w)
}
}
}
func TestKeyIndexFindGeneration(t *testing.T) {
ki := newTestKeyIndex(zaptest.NewLogger(t))
tests := []struct {
rev int64
wg *generation
}{
{0, nil},
{1, nil},
{2, &ki.generations[0]},
{3, &ki.generations[0]},
{4, &ki.generations[0]},
{5, &ki.generations[0]},
{6, nil},
{7, nil},
{8, &ki.generations[1]},
{9, &ki.generations[1]},
{10, &ki.generations[1]},
{11, &ki.generations[1]},
{12, nil},
{13, nil},
}
for i, tt := range tests {
g := ki.findGeneration(tt.rev)
if g != tt.wg {
t.Errorf("#%d: generation = %+v, want %+v", i, g, tt.wg)
}
}
}
func TestKeyIndexLess(t *testing.T) {
ki := &keyIndex{key: []byte("foo")}
tests := []struct {
ki *keyIndex
w bool
}{
{&keyIndex{key: []byte("doo")}, false},
{&keyIndex{key: []byte("foo")}, false},
{&keyIndex{key: []byte("goo")}, true},
}
for i, tt := range tests {
g := ki.Less(tt.ki)
if g != tt.w {
t.Errorf("#%d: Less = %v, want %v", i, g, tt.w)
}
}
}
func TestGenerationIsEmpty(t *testing.T) {
tests := []struct {
g *generation
w bool
}{
{nil, true},
{&generation{}, true},
{&generation{revs: []Revision{{Main: 1}}}, false},
}
for i, tt := range tests {
g := tt.g.isEmpty()
if g != tt.w {
t.Errorf("#%d: isEmpty = %v, want %v", i, g, tt.w)
}
}
}
func TestGenerationWalk(t *testing.T) {
g := &generation{
ver: 3,
created: Revision{Main: 2},
revs: []Revision{{Main: 2}, {Main: 4}, {Main: 6}},
}
tests := []struct {
f func(rev Revision) bool
wi int
}{
{func(rev Revision) bool { return rev.Main >= 7 }, 2},
{func(rev Revision) bool { return rev.Main >= 6 }, 1},
{func(rev Revision) bool { return rev.Main >= 5 }, 1},
{func(rev Revision) bool { return rev.Main >= 4 }, 0},
{func(rev Revision) bool { return rev.Main >= 3 }, 0},
{func(rev Revision) bool { return rev.Main >= 2 }, -1},
}
for i, tt := range tests {
idx := g.walk(tt.f)
if idx != tt.wi {
t.Errorf("#%d: index = %d, want %d", i, idx, tt.wi)
}
}
}
func newTestKeyIndex(lg *zap.Logger) *keyIndex {
// key: "foo"
// modified: 16
// generations:
// {empty}
// {{14, 0}[1], {15, 1}[2], {16, 0}(t)[3]}
// {{8, 0}[1], {10, 0}[2], {12, 0}(t)[3]}
// {{2, 0}[1], {4, 0}[2], {6, 0}(t)[3]}
ki := &keyIndex{key: []byte("foo")}
ki.put(lg, 2, 0)
ki.put(lg, 4, 0)
ki.tombstone(lg, 6, 0)
ki.put(lg, 8, 0)
ki.put(lg, 10, 0)
ki.tombstone(lg, 12, 0)
ki.put(lg, 14, 0)
ki.put(lg, 15, 1)
ki.tombstone(lg, 16, 0)
return ki
}