Skip to content

Commit

Permalink
Merge pull request #2 from arg0net:fuzzing
Browse files Browse the repository at this point in the history
add PeekIndex and fuzz testing
  • Loading branch information
vgough authored Jun 10, 2024
2 parents d1e3423 + f501f86 commit 7354003
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 1 deletion.
13 changes: 13 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: '3'

tasks:
test:
desc: Run unit tests.
cmds:
- go test -v ./...

fuzz:
desc: Run fuzz tests.
cmds:
- go test -fuzz=Fuzz -fuzztime 30s .

5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ module github.com/arg0net/collections

go 1.22.3

require github.com/stretchr/testify v1.9.0
require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24
github.com/stretchr/testify v1.9.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
29 changes: 29 additions & 0 deletions ring.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,35 @@ func (r *Ring[T]) PopIndex(i int) (T, bool) {
return el, true
}

// PeekFront returns the first element in the ring without removing it.
func (r *Ring[T]) PeekFront() (T, bool) {
if len(r.right) == 0 {
var zero T
return zero, false
}
return r.right[0], true
}

// PeekIndex returns the element at the given index without removing it.
// If the index is out of bounds, it returns false.
// The index is 0-based, with 0 being the first element in the ring.
// PeekIndex(0) is equivalent to PeekFront.
func (r *Ring[T]) PeekIndex(i int) (T, bool) {
if i == 0 {
return r.PeekFront()
}
if i < 0 || i >= r.Len() {
var zero T
return zero, false
}

idx := i - len(r.right)
if idx >= 0 {
return r.left[idx], true
}
return r.right[i], true
}

// Len returns the number of elements in the ring.
func (r *Ring[T]) Len() int {
return len(r.left) + len(r.right)
Expand Down
139 changes: 139 additions & 0 deletions ring_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package collections_test
import (
"testing"

fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/stretchr/testify/require"

"github.com/arg0net/collections"
Expand Down Expand Up @@ -144,3 +145,141 @@ func BenchmarkRing(b *testing.B) {
nextWrite++
}
}

// fakeRing is a simplified implementation of a buffer used for fuzzing tests.
// This behaves like a ring buffer, but it's not optimized for performance.
type fakeRing struct {
elements []int
}

func (r *fakeRing) PushBack(e int) bool {
if len(r.elements) == cap(r.elements) {
return false
}
r.elements = append(r.elements, e)
return true
}

func (r *fakeRing) PopFront() (int, bool) {
if len(r.elements) == 0 {
return 0, false
}
el := r.elements[0]
copy(r.elements, r.elements[1:])
r.elements = r.elements[:len(r.elements)-1]
return el, true
}

func (r *fakeRing) Copy(out []int) int {
return copy(out, r.elements)
}

func (r *fakeRing) Len() int {
return len(r.elements)
}

func (r *fakeRing) PopIndex(i int) (int, bool) {
if i < 0 || i >= len(r.elements) {
return 0, false
}
el := r.elements[i]
r.elements = append(r.elements[:i], r.elements[i+1:]...)
return el, true
}

func (r *fakeRing) PeekIndex(idx int) (int, bool) {
if idx < 0 || idx >= len(r.elements) {
return 0, false
}
return r.elements[idx], true
}

type ringOp int

const (
pushBack ringOp = iota
popFront
popIndex
peekIndex
lastOpForCounting // keep last
)

func dup[T any](s []T) []T {
out := make([]T, len(s))
copy(out, s)
return out
}

func FuzzRing(f *testing.F) {
init := []int{1, 2, 3, 4, 5}

f.Fuzz(func(t *testing.T, data []byte) {
fake := &fakeRing{elements: dup(init)}
real := collections.NewRing[int](len(init))
for _, v := range init {
real.PushBack(v)
}

fz := fuzz.NewConsumer(data)
var ops []ringOp
err := fz.CreateSlice(&ops)
if err != nil {
return
}

var buf1, buf2 [5]int
for i := 0; i < len(ops); i++ {
switch ops[i] % lastOpForCounting {
case pushBack:
var value int
if i+1 < len(ops) {
value = int(ops[i+1])
i++
}
t.Logf("pushBack %d", value)
ok1 := fake.PushBack(value)
ok2 := real.PushBack(value)
if ok1 != ok2 {
t.Fatalf("pushBack differs: %v vs %v in %v vs %v", ok1, ok2, fake, real)
}
case popFront:
t.Logf("popFront")
f1, ok1 := fake.PopFront()
r1, ok2 := real.PopFront()
if f1 != r1 || ok1 != ok2 {
t.Fatalf("popFront differs: %v vs %v in %v vs %v", f1, r1, fake, real)
}
case popIndex:
var idx int
if i+1 < len(ops) {
idx = int(ops[i+1])
i++
}
t.Logf("popIndex %d", idx)
f1, ok1 := fake.PopIndex(idx)
r1, ok2 := real.PopIndex(idx)
if f1 != r1 || ok1 != ok2 {
t.Fatalf("popIndex differs: %v vs %v in %v vs %v", f1, r1, fake, real)
}
case peekIndex:
var idx int
if i+1 < len(ops) {
idx = int(ops[i+1])
i++
}
t.Logf("peekIndex %d", idx)
f1, ok1 := fake.PeekIndex(idx)
r1, ok2 := real.PeekIndex(idx)
if f1 != r1 || ok1 != ok2 {
t.Fatalf("peekIndex differs: %v vs %v in %v vs %v", f1, r1, fake, real)
}
}
if fake.Copy(buf1[:]) != real.Copy(buf2[:]) {
t.Fatalf("copy differs")
}
if buf1 != buf2 {
t.Fatalf("buffers differ: %v vs %v", buf1, buf2)
}
}
})
}

0 comments on commit 7354003

Please sign in to comment.