testing/quick tests for functionality in bits

Change-Id: Ie1250f12eb55df32ad4116e1f3255ffa29c6919b
diff --git a/bits_test.go b/bits_test.go
index 5af9adc..41710a9 100644
--- a/bits_test.go
+++ b/bits_test.go
@@ -15,9 +15,15 @@
 package zoekt
 
 import (
+	"encoding/binary"
 	"log"
+	"math/rand"
 	"reflect"
+	"sort"
 	"testing"
+	"testing/quick"
+
+	"github.com/google/go-cmp/cmp"
 )
 
 var _ = log.Println
@@ -28,6 +34,31 @@
 	if n.String() != "abc" {
 		t.Errorf("got %q, want %q", n, "abc")
 	}
+
+	f := func(b ngramRunes) bool {
+		n := runesToNGram(b)
+		got := ngramRunes(ngramToRunes(n))
+		if !reflect.DeepEqual(b, got) {
+			t.Log(cmp.Diff(b, got))
+			return false
+		}
+		return true
+	}
+	if err := quick.Check(f, nil); err != nil {
+		t.Error(err)
+	}
+}
+
+type ngramRunes [ngramSize]rune
+
+func (ngramRunes) Generate(rand *rand.Rand, size int) reflect.Value {
+	// Same implementation used by testing/quick to generate strings. But we
+	// force it to ngramSize runes.
+	var b ngramRunes
+	for i := range b {
+		b[i] = rune(rand.Intn(0x10ffff))
+	}
+	return reflect.ValueOf(b)
 }
 
 func TestDocSection(t *testing.T) {
@@ -76,3 +107,92 @@
 		}
 	}
 }
+
+func TestSizedDeltas(t *testing.T) {
+	encode := func(nums []uint32) []byte {
+		return toSizedDeltas(nums)
+	}
+	decode := func(data []byte) []uint32 {
+		if len(data) == 0 {
+			return nil
+		}
+		return fromSizedDeltas(data, nil)
+	}
+	testIncreasingIntCoder(t, encode, decode)
+}
+
+func TestFromDeltas(t *testing.T) {
+	decode := func(data []byte) []uint32 {
+		if len(data) == 0 {
+			return nil
+		}
+		return fromDeltas(data, nil)
+	}
+	testIncreasingIntCoder(t, toDeltas, decode)
+}
+
+func TestCompressedPostingIterator(t *testing.T) {
+	decode := func(data []byte) []uint32 {
+		if len(data) == 0 {
+			return nil
+		}
+
+		var nums []uint32
+		i := newCompressedPostingIterator(data, stringToNGram("abc"))
+		for i.first() != maxUInt32 {
+			nums = append(nums, i.first())
+			i.next(i.first())
+		}
+		return nums
+	}
+	testIncreasingIntCoder(t, toDeltas, decode)
+}
+
+func toDeltas(offsets []uint32) []byte {
+	var enc [8]byte
+
+	deltas := make([]byte, 0, len(offsets)*2)
+
+	var last uint32
+	for _, p := range offsets {
+		delta := p - last
+		last = p
+
+		m := binary.PutUvarint(enc[:], uint64(delta))
+		deltas = append(deltas, enc[:m]...)
+	}
+	return deltas
+}
+
+func testIncreasingIntCoder(t *testing.T, encode func([]uint32) []byte, decode func([]byte) []uint32) {
+	f := func(nums []uint32) bool {
+		nums = sortedUnique(nums)
+		b := encode(nums)
+		got := decode(b)
+		if len(nums) == len(got) && len(nums) == 0 {
+			return true
+		}
+		if !reflect.DeepEqual(got, nums) {
+			t.Log(cmp.Diff(nums, got))
+			return false
+		}
+		return true
+	}
+	if err := quick.Check(f, nil); err != nil {
+		t.Error(err)
+	}
+}
+
+func sortedUnique(nums []uint32) []uint32 {
+	if len(nums) == 0 {
+		return nums
+	}
+	sort.Slice(nums, func(i, j int) bool { return nums[i] < nums[j] })
+	filtered := nums[:1]
+	for _, n := range nums[1:] {
+		if filtered[len(filtered)-1] != n {
+			filtered = append(filtered, n)
+		}
+	}
+	return filtered
+}
diff --git a/go.mod b/go.mod
index e7fab0d..6eb793c 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@
 	github.com/andygrunwald/go-gerrit v0.0.0-20181026193842-43cfd7a94eb4
 	github.com/fsnotify/fsnotify v1.4.7
 	github.com/gfleury/go-bitbucket-v1 v0.0.0-20181102191809-4910839b609e
+	github.com/google/go-cmp v0.3.0
 	github.com/google/go-github v17.0.0+incompatible
 	github.com/google/slothfs v0.0.0-20170112234537-ecdd255f653d
 	github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
diff --git a/go.sum b/go.sum
index 18fcccd..442bc74 100644
--- a/go.sum
+++ b/go.sum
@@ -20,6 +20,8 @@
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
 github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=