blob: 53311f2c0628a6555145afd568ec36502464ce51 [file] [log] [blame]
// Copyright 2014 Google LLC
// Modified 2018 by Jonathan Amsterdam (jbamsterdam@gmail.com)
//
// 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 btree
import (
"flag"
"math/rand"
"os"
"sort"
"sync"
"testing"
"time"
"github.com/google/go-cmp/cmp"
)
func init() {
seed := time.Now().Unix()
rand.Seed(seed)
}
type itemWithIndex struct {
Key Key
Value Value
Index int
}
// perm returns a random permutation of n Int items in the range [0, n).
func perm(n int) []itemWithIndex {
var out []itemWithIndex
for _, v := range rand.Perm(n) {
out = append(out, itemWithIndex{v, v, v})
}
return out
}
// rang returns an ordered list of Int items in the range [0, n).
func rang(n int) []itemWithIndex {
var out []itemWithIndex
for i := 0; i < n; i++ {
out = append(out, itemWithIndex{i, i, i})
}
return out
}
// all extracts all items from an iterator.
func all(it *Iterator) []itemWithIndex {
var out []itemWithIndex
for it.Next() {
out = append(out, itemWithIndex{it.Key, it.Value, it.Index})
}
return out
}
func reverse(s []itemWithIndex) {
for i := 0; i < len(s)/2; i++ {
s[i], s[len(s)-i-1] = s[len(s)-i-1], s[i]
}
}
var btreeDegree = flag.Int("degree", 32, "B-Tree degree")
func TestBTree(t *testing.T) {
tr := New(*btreeDegree, less)
const treeSize = 10000
for i := 0; i < 10; i++ {
if min, _ := tr.Min(); min != nil {
t.Fatalf("empty min, got %+v", min)
}
if max, _ := tr.Max(); max != nil {
t.Fatalf("empty max, got %+v", max)
}
for _, m := range perm(treeSize) {
if _, ok := tr.Set(m.Key, m.Value); ok {
t.Fatal("set found item", m)
}
}
for _, m := range perm(treeSize) {
_, ok, idx := tr.SetWithIndex(m.Key, m.Value)
if !ok {
t.Fatal("set didn't find item", m)
}
if idx != m.Index {
t.Fatalf("got index %d, want %d", idx, m.Index)
}
}
mink, minv := tr.Min()
if want := 0; mink != want || minv != want {
t.Fatalf("min: want %+v, got %+v, %+v", want, mink, minv)
}
maxk, maxv := tr.Max()
if want := treeSize - 1; maxk != want || maxv != want {
t.Fatalf("max: want %+v, got %+v, %+v", want, maxk, maxv)
}
got := all(tr.BeforeIndex(0))
want := rang(treeSize)
if !cmp.Equal(got, want) {
t.Fatalf("mismatch:\n got: %v\nwant: %v", got, want)
}
for _, m := range perm(treeSize) {
if _, removed := tr.Delete(m.Key); !removed {
t.Fatalf("didn't find %v", m)
}
}
if got = all(tr.BeforeIndex(0)); len(got) > 0 {
t.Fatalf("some left!: %v", got)
}
}
}
func TestAt(t *testing.T) {
tr := New(*btreeDegree, less)
for _, m := range perm(100) {
tr.Set(m.Key, m.Value)
}
for i := 0; i < tr.Len(); i++ {
gotk, gotv := tr.At(i)
if want := i; gotk != want || gotv != want {
t.Fatalf("At(%d) = (%v, %v), want (%v, %v)", i, gotk, gotv, want, want)
}
}
}
func TestGetWithIndex(t *testing.T) {
tr := New(*btreeDegree, less)
for _, m := range perm(100) {
tr.Set(m.Key, m.Value)
}
for i := 0; i < tr.Len(); i++ {
gotv, goti := tr.GetWithIndex(i)
wantv, wanti := i, i
if gotv != wantv || goti != wanti {
t.Errorf("GetWithIndex(%d) = (%v, %v), want (%v, %v)",
i, gotv, goti, wantv, wanti)
}
}
_, got := tr.GetWithIndex(100)
if want := -1; got != want {
t.Errorf("got %d, want %d", got, want)
}
}
func TestSetWithIndex(t *testing.T) {
tr := New(4, less) // use a small degree to cover more cases
var contents []int
for _, m := range perm(100) {
_, _, idx := tr.SetWithIndex(m.Key, m.Value)
contents = append(contents, m.Index)
sort.Ints(contents)
want := -1
for i, c := range contents {
if c == m.Index {
want = i
break
}
}
if idx != want {
t.Fatalf("got %d, want %d", idx, want)
}
}
}
func TestDeleteMin(t *testing.T) {
tr := New(3, less)
for _, m := range perm(100) {
tr.Set(m.Key, m.Value)
}
var got []itemWithIndex
for i := 0; tr.Len() > 0; i++ {
k, v := tr.DeleteMin()
got = append(got, itemWithIndex{k, v, i})
}
if want := rang(100); !cmp.Equal(got, want) {
t.Fatalf("got: %v\nwant: %v", got, want)
}
}
func TestDeleteMax(t *testing.T) {
tr := New(3, less)
for _, m := range perm(100) {
tr.Set(m.Key, m.Value)
}
var got []itemWithIndex
for tr.Len() > 0 {
k, v := tr.DeleteMax()
got = append(got, itemWithIndex{k, v, tr.Len()})
}
reverse(got)
if want := rang(100); !cmp.Equal(got, want) {
t.Fatalf("got: %v\nwant: %v", got, want)
}
}
func TestIterator(t *testing.T) {
const size = 10
tr := New(2, less)
// Empty tree.
for i, it := range []*Iterator{
tr.BeforeIndex(0),
tr.Before(3),
tr.After(3),
} {
if got, want := it.Next(), false; got != want {
t.Errorf("empty, #%d: got %t, want %t", i, got, want)
}
}
// Root with zero children.
tr.Set(1, nil)
tr.Delete(1)
if !(tr.root != nil && len(tr.root.children) == 0 && len(tr.root.items) == 0) {
t.Fatal("wrong shape tree")
}
for i, it := range []*Iterator{
tr.BeforeIndex(0),
tr.Before(3),
tr.After(3),
} {
if got, want := it.Next(), false; got != want {
t.Errorf("zero root, #%d: got %t, want %t", i, got, want)
}
}
// Tree with size elements.
p := perm(size)
for _, v := range p {
tr.Set(v.Key, v.Value)
}
it := tr.BeforeIndex(0)
got := all(it)
want := rang(size)
if !cmp.Equal(got, want) {
t.Fatalf("got %+v\nwant %+v\n", got, want)
}
for i, w := range want {
it := tr.Before(w.Key)
got = all(it)
wn := want[w.Key.(int):]
if !cmp.Equal(got, wn) {
t.Fatalf("got %+v\nwant %+v\n", got, wn)
}
it = tr.BeforeIndex(i)
got = all(it)
if !cmp.Equal(got, wn) {
t.Fatalf("got %+v\nwant %+v\n", got, wn)
}
it = tr.After(w.Key)
got = all(it)
wn = append([]itemWithIndex(nil), want[:w.Key.(int)+1]...)
reverse(wn)
if !cmp.Equal(got, wn) {
t.Fatalf("got %+v\nwant %+v\n", got, wn)
}
it = tr.AfterIndex(i)
got = all(it)
if !cmp.Equal(got, wn) {
t.Fatalf("got %+v\nwant %+v\n", got, wn)
}
}
// Non-existent keys.
tr = New(2, less)
for _, v := range p {
tr.Set(v.Key.(int)*2, v.Value)
}
// tr has only even keys: 0, 2, 4, ... Iterate from odd keys.
for i := -1; i <= size+1; i += 2 {
it := tr.Before(i)
got := all(it)
var want []itemWithIndex
for j := (i + 1) / 2; j < size; j++ {
want = append(want, itemWithIndex{j * 2, j, j})
}
if !cmp.Equal(got, want) {
tr.print(os.Stdout)
t.Fatalf("%d: got %+v\nwant %+v\n", i, got, want)
}
it = tr.After(i)
got = all(it)
want = nil
for j := (i - 1) / 2; j >= 0; j-- {
want = append(want, itemWithIndex{j * 2, j, j})
}
if !cmp.Equal(got, want) {
t.Fatalf("%d: got %+v\nwant %+v\n", i, got, want)
}
}
}
func TestMixed(t *testing.T) {
// Test random, mixed insertions and deletions.
const maxSize = 1000
tr := New(3, less)
has := map[int]bool{}
for i := 0; i < 10000; i++ {
r := rand.Intn(maxSize)
if r >= tr.Len() {
old, ok := tr.Set(r, r)
if has[r] != ok {
t.Fatalf("%d: has=%t, ok=%t", r, has[r], ok)
}
if ok && old.(int) != r {
t.Fatalf("%d: bad old", r)
}
has[r] = true
if got, want := tr.Get(r), r; got != want {
t.Fatalf("Get(%d) = %d, want %d", r, got, want)
}
} else {
// Expoit random map iteration order.
var d int
for d = range has {
break
}
old, removed := tr.Delete(d)
if !removed {
t.Fatalf("%d not found", d)
}
if old.(int) != d {
t.Fatalf("%d: bad old", d)
}
delete(has, d)
}
}
}
const cloneTestSize = 10000
func cloneTest(t *testing.T, b *BTree, start int, p []itemWithIndex, wg *sync.WaitGroup, treec chan<- *BTree) {
treec <- b
for i := start; i < cloneTestSize; i++ {
b.Set(p[i].Key, p[i].Value)
if i%(cloneTestSize/5) == 0 {
wg.Add(1)
go cloneTest(t, b.Clone(), i+1, p, wg, treec)
}
}
wg.Done()
}
func TestCloneConcurrentOperations(t *testing.T) {
b := New(*btreeDegree, less)
treec := make(chan *BTree)
p := perm(cloneTestSize)
var wg sync.WaitGroup
wg.Add(1)
go cloneTest(t, b, 0, p, &wg, treec)
var trees []*BTree
donec := make(chan struct{})
go func() {
for t := range treec {
trees = append(trees, t)
}
close(donec)
}()
wg.Wait()
close(treec)
<-donec
want := rang(cloneTestSize)
for i, tree := range trees {
if !cmp.Equal(want, all(tree.BeforeIndex(0))) {
t.Errorf("tree %v mismatch", i)
}
}
toRemove := rang(cloneTestSize)[cloneTestSize/2:]
for i := 0; i < len(trees)/2; i++ {
tree := trees[i]
wg.Add(1)
go func() {
for _, m := range toRemove {
tree.Delete(m.Key)
}
wg.Done()
}()
}
wg.Wait()
for i, tree := range trees {
var wantpart []itemWithIndex
if i < len(trees)/2 {
wantpart = want[:cloneTestSize/2]
} else {
wantpart = want
}
if got := all(tree.BeforeIndex(0)); !cmp.Equal(wantpart, got) {
t.Errorf("tree %v mismatch, want %v got %v", i, len(want), len(got))
}
}
}
func less(a, b interface{}) bool { return a.(int) < b.(int) }