1
0
buffer/pool_test.go

461 lines
8.6 KiB
Go
Raw Normal View History

2026-02-17 16:58:00 +01:00
package buffer_test
import (
"testing"
"math/rand"
"code.squareroundforest.org/arpio/buffer"
"bytes"
"io"
"errors"
)
type pool struct {
allocSize int
alloc, free int
errAfter []int
zeroAfter []int
shortAfter []int
longAfter []int
randomSize []int
}
func (p pool) allocCondition(c *[]int) bool {
if len(*c) == 0 {
return false
}
if p.alloc < (*c)[0] {
return false
}
*c = (*c)[1:]
return true
}
func (p *pool) Get() ([]byte, error) {
defer func() {
p.alloc++
}()
if p.allocCondition(&p.errAfter) {
return nil, errTest
}
if p.allocCondition(&p.zeroAfter) {
return nil, nil
}
n := p.allocSize
if p.allocCondition(&p.shortAfter) {
n /= 2
}
if p.allocCondition(&p.longAfter) {
n *= 2
}
if len(p.randomSize) > 1 {
n = p.randomSize[0] + rand.Intn(p.randomSize[1])
}
if len(p.randomSize) == 1 {
n = 1 + rand.Intn(p.randomSize[0])
}
return make([]byte, n), nil
}
func (p *pool) Put([]byte) {
p.free++
}
func TestPoolUsage(t *testing.T) {
t.Run("allocate", func(t *testing.T) {
t.Run("read", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{allocSize: 1 << 12}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
b := make([]byte, 256)
n, err := r.Read(b)
if err != nil {
t.Fatal(err)
}
if n != len(b) {
t.Fatal("invalid read length")
}
if !bytes.Equal(b, generate(len(b))) {
t.Fatal("invalid content")
}
if p.alloc != 1 {
t.Fatal("invalid allocation count")
}
})
t.Run("read bytes", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{allocSize: 1 << 9}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
b, ok, err := r.ReadBytes([]byte("123"), 1 << 12)
if err != nil {
t.Fatal(err)
}
if ok {
t.Fatal("unexpected delimiter")
}
if len(b) != 0 {
t.Fatal("invalid content")
}
if p.alloc != 8 {
t.Fatal("invalid allocation count")
}
})
t.Run("read utf8", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{allocSize: 1 << 12}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
runes, n, err := r.ReadUTF8(1 << 12)
if err != nil {
t.Fatal(err)
}
if n != 1 << 12 {
t.Fatal("unexpected delimiter")
}
if len(runes) != 1 << 12 {
t.Fatal("invalid content")
}
if p.alloc != 3 {
t.Fatal("invalid allocation count")
}
})
t.Run("peek", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{allocSize: 1 << 12}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
b, err := r.Peek(3 * 1 << 12)
if err != nil {
t.Fatal(err)
}
if len(b) != 3 * 1 << 12 {
t.Fatal("invalid content")
}
if p.alloc != 3 {
t.Fatal("invalid allocation count")
}
})
t.Run("buffered", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{allocSize: 1 << 12}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
b := r.Buffered()
if len(b) != 0 {
t.Fatal("invalid content")
}
if p.alloc != 0 {
t.Fatal("invalid allocation count")
}
})
t.Run("write to", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{allocSize: 1 << 12}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
var b bytes.Buffer
n, err := r.WriteTo(&b)
if err != nil {
t.Fatal(err)
}
if n != 1 << 15 {
t.Fatal("invalid write length")
}
if !bytes.Equal(b.Bytes(), generate(1 << 15)) {
t.Fatal("invalid content")
}
if p.alloc != 1 {
t.Fatal("invalid allocation count")
}
})
})
t.Run("free", func(t *testing.T) {
t.Run("read", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{allocSize: 1 << 12}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
b := make([]byte, 1 << 9)
for {
_, err := r.Read(b)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
t.Fatal(err)
}
}
if p.alloc != 1 {
t.Fatal("invalid allocation count")
}
if p.free != 1 {
t.Fatal("invalid free count")
}
})
t.Run("read bytes", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{allocSize: 1 << 12}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
_, _, err := r.ReadBytes([]byte("123"), 1 << 15 + 3)
if err != nil {
t.Fatal(err)
}
_, _, err = r.ReadBytes([]byte("123"), 1 << 15 + 3)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if p.alloc != 9 {
t.Fatal("invalid allocation count", p.alloc)
}
if p.free != 9 {
t.Fatal("invalid free count", p.free)
}
})
t.Run("read utf8", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{allocSize: 1 << 12}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
for {
runes, n, err := r.ReadUTF8(1 << 12)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
t.Fatal(err)
}
if n != 1 << 12 {
t.Fatal("unexpected delimiter")
}
if len(runes) != 1 << 12 {
t.Fatal("invalid content")
}
}
if p.alloc != p.free {
t.Fatal("invalid allocation count", p.alloc, p.free)
}
})
t.Run("peek", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{allocSize: 1 << 12}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
b, err := r.Peek(3 * 1 << 12)
if err != nil {
t.Fatal(err)
}
if len(b) != 3 * 1 << 12 {
t.Fatal("invalid content")
}
if p.alloc != 3 {
t.Fatal("invalid allocation count")
}
if p.free != 0 {
t.Fatal("invalid allocation count")
}
b = make([]byte, 1 << 9)
for {
_, err := r.Read(b)
if errors.Is(err, io.EOF) {
break
}
if err != nil {
t.Fatal(err)
}
}
if p.alloc != 3 {
t.Fatal("invalid allocation count")
}
if p.free != 3 {
t.Fatal("invalid allocation count")
}
})
t.Run("buffered", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{allocSize: 1 << 12}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
b := r.Buffered()
if len(b) != 0 {
t.Fatal("invalid content")
}
if p.alloc != 0 || p.free != 0 {
t.Fatal("invalid allocation count")
}
})
t.Run("write to", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{allocSize: 1 << 12}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
var b bytes.Buffer
n, err := r.WriteTo(&b)
if err != nil {
t.Fatal(err)
}
if n != 1 << 15 {
t.Fatal("invalid write length")
}
if !bytes.Equal(b.Bytes(), generate(1 << 15)) {
t.Fatal("invalid content")
}
if p.alloc != 1 {
t.Fatal("invalid allocation count")
}
if p.free != 1 {
t.Fatal("invalid free count")
}
})
})
t.Run("null segment", func(t *testing.T) {
t.Run("read", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{
allocSize: 1 << 12,
zeroAfter: []int{0},
}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
b := make([]byte, 256)
_, err := r.Read(b)
if !errors.Is(err, buffer.ErrZeroAllocation) {
t.Fatal("failed to fail", err)
}
})
t.Run("read bytes", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{
allocSize: 1 << 9,
zeroAfter: []int{1},
}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
_, _, err := r.ReadBytes([]byte("123"), 1 << 12)
if err != nil {
t.Fatal(err)
}
_, _, err = r.ReadBytes([]byte("123"), 1 << 12)
if !errors.Is(err, buffer.ErrZeroAllocation) {
t.Fatal("failed to fail", err)
}
})
t.Run("read utf8", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{
allocSize: 1 << 12,
zeroAfter: []int{0},
}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
_, _, err := r.ReadUTF8(1 << 12)
if !errors.Is(err, buffer.ErrZeroAllocation) {
t.Fatal("failed to fail", err)
}
})
t.Run("peek", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{
allocSize: 1 << 12,
zeroAfter: []int{0},
}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
_, err := r.Peek(3 * 1 << 12)
if !errors.Is(err, buffer.ErrZeroAllocation) {
t.Fatal("failed to fail", err)
}
})
t.Run("write to", func(t *testing.T) {
g := &gen{max: 1 << 15}
p := &pool{
allocSize: 1 << 12,
zeroAfter: []int{0},
}
o := buffer.Options{Pool: p}
r := buffer.ReaderFrom(g, o)
var b bytes.Buffer
_, err := r.WriteTo(&b)
if !errors.Is(err, buffer.ErrZeroAllocation) {
t.Fatal("failed to fail", err)
}
})
})
// varying segment sizes: read bytes, peek
// one byte segments: read, read bytes, read utf8, peek, write to
// pool error on allocate: read, read bytes, read utf8, peek, write to
}