johnsonjh/jleveldb

View on GitHub
leveldb/testutil/iter.go

Summary

Maintainability
A
2 hrs
Test Coverage
// Copyright © 2014, Suryandaru Triandana <syndtr@gmail.com>
// Copyright © 2021, Jeffrey H. Johnson <trnsz@pobox.com>
//
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package testutil

import (
    "fmt"
    "math/rand"

    . "github.com/onsi/gomega"

    "github.com/johnsonjh/jleveldb/leveldb/iterator"
)

type IterAct int

func (a IterAct) String() string {
    switch a {
    case IterNone:
        return "none"
    case IterFirst:
        return "first"
    case IterLast:
        return "last"
    case IterPrev:
        return "prev"
    case IterNext:
        return "next"
    case IterSeek:
        return "seek"
    case IterSOI:
        return "soi"
    case IterEOI:
        return "eoi"
    }
    return "unknown"
}

const (
    IterNone IterAct = iota
    IterFirst
    IterLast
    IterPrev
    IterNext
    IterSeek
    IterSOI
    IterEOI
)

type IteratorTesting struct {
    KeyValue
    Iter         iterator.Iterator
    Rand         *rand.Rand
    PostFn       func(t *IteratorTesting)
    Pos          int
    Act, LastAct IterAct

    once bool
}

func (t *IteratorTesting) init() {
    if !t.once {
        t.Pos = -1
        t.once = true
    }
}

func (t *IteratorTesting) post() {
    if t.PostFn != nil {
        t.PostFn(t)
    }
}

func (t *IteratorTesting) setAct(act IterAct) {
    t.LastAct, t.Act = t.Act, act
}

func (t *IteratorTesting) text() string {
    return fmt.Sprintf("at pos %d and last action was <%v> -> <%v>", t.Pos, t.LastAct, t.Act)
}

func (t *IteratorTesting) Text() string {
    return "IteratorTesting is " + t.text()
}

func (t *IteratorTesting) IsFirst() bool {
    t.init()
    return t.Len() > 0 && t.Pos == 0
}

func (t *IteratorTesting) IsLast() bool {
    t.init()
    return t.Len() > 0 && t.Pos == t.Len()-1
}

func (t *IteratorTesting) TestKV() {
    t.init()
    key, value := t.Index(t.Pos)
    Expect(t.Iter.Key()).NotTo(BeNil())
    Expect(t.Iter.Key()).Should(Equal(key), "Key is invalid, %s", t.text())
    Expect(t.Iter.Value()).Should(Equal(value), "Value for key %q, %s", key, t.text())
}

func (t *IteratorTesting) First() {
    t.init()
    t.setAct(IterFirst)

    ok := t.Iter.First()
    Expect(t.Iter.Error()).ShouldNot(HaveOccurred())
    if t.Len() > 0 {
        t.Pos = 0
        Expect(ok).Should(BeTrue(), t.Text())
        t.TestKV()
    } else {
        t.Pos = -1
        Expect(ok).ShouldNot(BeTrue(), t.Text())
    }
    t.post()
}

func (t *IteratorTesting) Last() {
    t.init()
    t.setAct(IterLast)

    ok := t.Iter.Last()
    Expect(t.Iter.Error()).ShouldNot(HaveOccurred())
    if t.Len() > 0 {
        t.Pos = t.Len() - 1
        Expect(ok).Should(BeTrue(), t.Text())
        t.TestKV()
    } else {
        t.Pos = 0
        Expect(ok).ShouldNot(BeTrue(), t.Text())
    }
    t.post()
}

func (t *IteratorTesting) Next() {
    t.init()
    t.setAct(IterNext)

    ok := t.Iter.Next()
    Expect(t.Iter.Error()).ShouldNot(HaveOccurred())
    if t.Pos < t.Len()-1 {
        t.Pos++
        Expect(ok).Should(BeTrue(), t.Text())
        t.TestKV()
    } else {
        t.Pos = t.Len()
        Expect(ok).ShouldNot(BeTrue(), t.Text())
    }
    t.post()
}

func (t *IteratorTesting) Prev() {
    t.init()
    t.setAct(IterPrev)

    ok := t.Iter.Prev()
    Expect(t.Iter.Error()).ShouldNot(HaveOccurred())
    if t.Pos > 0 {
        t.Pos--
        Expect(ok).Should(BeTrue(), t.Text())
        t.TestKV()
    } else {
        t.Pos = -1
        Expect(ok).ShouldNot(BeTrue(), t.Text())
    }
    t.post()
}

func (t *IteratorTesting) Seek(i int) {
    t.init()
    t.setAct(IterSeek)

    key, _ := t.Index(i)
    oldKey, _ := t.IndexOrNil(t.Pos)

    ok := t.Iter.Seek(key)
    Expect(t.Iter.Error()).ShouldNot(HaveOccurred())
    Expect(ok).Should(BeTrue(), fmt.Sprintf("Seek from key %q to %q, to pos %d, %s", oldKey, key, i, t.text()))

    t.Pos = i
    t.TestKV()
    t.post()
}

func (t *IteratorTesting) SeekInexact(i int) {
    t.init()
    t.setAct(IterSeek)
    var key0 []byte
    key1, _ := t.Index(i)
    if i > 0 {
        key0, _ = t.Index(i - 1)
    }
    key := BytesSeparator(key0, key1)
    oldKey, _ := t.IndexOrNil(t.Pos)

    ok := t.Iter.Seek(key)
    Expect(t.Iter.Error()).ShouldNot(HaveOccurred())
    Expect(ok).Should(BeTrue(), fmt.Sprintf("Seek from key %q to %q (%q), to pos %d, %s", oldKey, key, key1, i, t.text()))

    t.Pos = i
    t.TestKV()
    t.post()
}

func (t *IteratorTesting) SeekKey(key []byte) {
    t.init()
    t.setAct(IterSeek)
    oldKey, _ := t.IndexOrNil(t.Pos)
    i := t.Search(key)

    ok := t.Iter.Seek(key)
    Expect(t.Iter.Error()).ShouldNot(HaveOccurred())
    if i < t.Len() {
        key_, _ := t.Index(i)
        Expect(ok).Should(BeTrue(), fmt.Sprintf("Seek from key %q to %q (%q), to pos %d, %s", oldKey, key, key_, i, t.text()))
        t.Pos = i
        t.TestKV()
    } else {
        Expect(ok).ShouldNot(BeTrue(), fmt.Sprintf("Seek from key %q to %q, %s", oldKey, key, t.text()))
    }

    t.Pos = i
    t.post()
}

func (t *IteratorTesting) SOI() {
    t.init()
    t.setAct(IterSOI)
    Expect(t.Pos).Should(BeNumerically("<=", 0), t.Text())
    for i := 0; i < 3; i++ {
        t.Prev()
    }
    t.post()
}

func (t *IteratorTesting) EOI() {
    t.init()
    t.setAct(IterEOI)
    Expect(t.Pos).Should(BeNumerically(">=", t.Len()-1), t.Text())
    for i := 0; i < 3; i++ {
        t.Next()
    }
    t.post()
}

func (t *IteratorTesting) WalkPrev(fn func(t *IteratorTesting)) {
    t.init()
    for old := t.Pos; t.Pos > 0; old = t.Pos {
        fn(t)
        Expect(t.Pos).Should(BeNumerically("<", old), t.Text())
    }
}

func (t *IteratorTesting) WalkNext(fn func(t *IteratorTesting)) {
    t.init()
    for old := t.Pos; t.Pos < t.Len()-1; old = t.Pos {
        fn(t)
        Expect(t.Pos).Should(BeNumerically(">", old), t.Text())
    }
}

func (t *IteratorTesting) PrevAll() {
    t.WalkPrev(func(t *IteratorTesting) {
        t.Prev()
    })
}

func (t *IteratorTesting) NextAll() {
    t.WalkNext(func(t *IteratorTesting) {
        t.Next()
    })
}

func DoIteratorTesting(t *IteratorTesting) {
    if t.Rand == nil {
        t.Rand = NewRand()
    }
    t.SOI()
    t.NextAll()
    t.First()
    t.SOI()
    t.NextAll()
    t.EOI()
    t.PrevAll()
    t.Last()
    t.EOI()
    t.PrevAll()
    t.SOI()

    t.NextAll()
    t.PrevAll()
    t.NextAll()
    t.Last()
    t.PrevAll()
    t.First()
    t.NextAll()
    t.EOI()

    ShuffledIndex(t.Rand, t.Len(), 1, func(i int) {
        t.Seek(i)
    })

    ShuffledIndex(t.Rand, t.Len(), 1, func(i int) {
        t.SeekInexact(i)
    })

    ShuffledIndex(t.Rand, t.Len(), 1, func(i int) {
        t.Seek(i)
        if i%2 != 0 {
            t.PrevAll()
            t.SOI()
        } else {
            t.NextAll()
            t.EOI()
        }
    })

    for _, key := range []string{"", "foo", "bar", "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"} {
        t.SeekKey([]byte(key))
    }
}