1
0
buffer/lib_test.go

915 lines
19 KiB
Go

package buffer_test
import (
"bufio"
"bytes"
"code.squareroundforest.org/arpio/buffer"
"errors"
"io"
"testing"
)
func TestLib(t *testing.T) {
t.Run("default pool", func(t *testing.T) {
t.Run("buffered reader", func(t *testing.T) {
g := &gen{max: 1 << 18}
r := buffer.BufferedReader(g, buffer.Options{})
b, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, generate(1<<18)) {
t.Fatal("output does not match", len(b))
}
})
t.Run("buffered content", func(t *testing.T) {
c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
g := &gen{max: 1 << 18}
return io.Copy(w, g)
})
r := buffer.BufferedContent(c, buffer.Options{})
b, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, generate(1<<18)) {
t.Fatal("output does not match", len(b))
}
})
})
t.Run("zero reader", func(t *testing.T) {
t.Run("buffered reader", func(t *testing.T) {
r := buffer.BufferedReader(nil, buffer.Options{})
b, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if len(b) != 0 {
t.Fatal("output does not match", len(b))
}
})
t.Run("buffered content", func(t *testing.T) {
r := buffer.BufferedContent(nil, buffer.Options{})
b, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if len(b) != 0 {
t.Fatal("output does not match", len(b))
}
})
})
t.Run("uninitialized reader", func(t *testing.T) {
t.Run("read", func(t *testing.T) {
var r buffer.Reader
p := make([]byte, 512)
n, err := r.Read(p)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if n != 0 {
t.Fatal(n)
}
})
t.Run("read bytes", func(t *testing.T) {
var r buffer.Reader
b, ok, err := r.ReadBytes([]byte("123"), 512)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if ok {
t.Fatal(ok)
}
if len(b) != 0 {
t.Fatal(len(b))
}
})
t.Run("read utf8", func(t *testing.T) {
var r buffer.Reader
runes, n, err := r.ReadUTF8(512)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if n != 0 {
t.Fatal(n)
}
if len(runes) != 0 {
t.Fatal(len(runes))
}
})
t.Run("peek", func(t *testing.T) {
var r buffer.Reader
b, err := r.Peek(512)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if len(b) != 0 {
t.Fatal(len(b))
}
})
t.Run("buffered", func(t *testing.T) {
var r buffer.Reader
b := r.Buffered()
if len(b) != 0 {
t.Fatal(len(b))
}
})
t.Run("write to", func(t *testing.T) {
var (
r buffer.Reader
b bytes.Buffer
)
n, err := r.WriteTo(&b)
if err != nil {
t.Fatal(err)
}
if n != 0 {
t.Fatal(n)
}
})
})
t.Run("abort", func(t *testing.T) {
t.Run("from blank state", func(t *testing.T) {
p := &fakePool{allocSize: 1 << 6}
r := buffer.BufferedReader(&gen{max: 1 << 12}, buffer.Options{Pool: p})
r.Close()
b := make([]byte, 1<<9)
n, err := r.Read(b)
if n != 0 || !errors.Is(err, buffer.ErrAbort) {
t.Fatal(n, err)
}
if p.alloc != 0 || p.free != 0 {
t.Fatal(p.alloc, p.free)
}
})
t.Run("with multiple segments", func(t *testing.T) {
p := &fakePool{allocSize: 1 << 6}
r := buffer.BufferedReader(&gen{max: 1 << 12}, buffer.Options{Pool: p})
b, ok, err := r.ReadBytes([]byte("123"), 1<<6+1<<5)
if ok || err != nil || p.alloc != 2 {
t.Fatal(len(b), ok, err, p.alloc)
}
r.Close()
b = make([]byte, 1<<9)
n, err := r.Read(b)
if n != 0 || !errors.Is(err, buffer.ErrAbort) {
t.Fatal(n, err)
}
if p.alloc != 2 || p.free != 2 {
t.Fatal(p.alloc, p.free)
}
})
t.Run("from err state", func(t *testing.T) {
p := &fakePool{allocSize: 1 << 6}
g := &gen{
max: 1 << 12,
errAfter: []int{1 << 6},
}
r := buffer.BufferedReader(g, buffer.Options{Pool: p})
b := make([]byte, 1<<9)
n, err := r.Read(b)
if n != 1<<6 || err != nil {
t.Fatal(n, err)
}
n, err = r.Read(b)
if n != 0 || !errors.Is(err, errTest) {
t.Fatal(n, err)
}
r.Close()
b = make([]byte, 1<<9)
n, err = r.Read(b)
if n != 0 || !errors.Is(err, errTest) {
t.Fatal(n, err)
}
if p.alloc != 1 || p.free != 1 {
t.Fatal(p.alloc, p.free)
}
})
t.Run("with content", func(t *testing.T) {
c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
var n int64
for i := 0; i < 3; i++ {
ni, err := w.Write([]byte("123456789")[i*3 : i*3+3])
n += int64(ni)
if err != nil {
return n, err
}
}
return n, nil
})
p := &fakePool{allocSize: 2}
o := buffer.Options{Pool: p}
r := buffer.BufferedContent(c, o)
b := make([]byte, 3)
for i := 0; i < 2; i++ {
n, err := r.Read(b)
if n != 3 || err != nil {
t.Fatal(n, err)
}
if string(b) != "123456789"[i*3:i*3+3] {
t.Fatal(string(b))
}
}
r.Close()
n, err := r.Read(b)
if n != 0 || !errors.Is(err, buffer.ErrAbort) {
t.Fatal(n, err)
}
if p.alloc != 1 || p.free != 1 {
t.Fatal(p.alloc, p.free)
}
})
t.Run("with content error", func(t *testing.T) {
c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
var n int64
for i := 0; i < 3; i++ {
ni, err := w.Write([]byte("123456789")[i*3 : i*3+3])
n += int64(ni)
if err != nil {
return n, err
}
}
return n, errTest
})
p := &fakePool{allocSize: 2}
o := buffer.Options{Pool: p}
r := buffer.BufferedContent(c, o)
b := make([]byte, 3)
for i := 0; i < 3; i++ {
n, err := r.Read(b)
if n != 3 || err != nil {
t.Fatal(n, err)
}
if string(b) != "123456789"[i*3:i*3+3] {
t.Fatal(string(b))
}
}
n, err := r.Read(b)
if n != 0 || !errors.Is(err, errTest) {
t.Fatal(n, err)
}
r.Close()
n, err = r.Read(b)
if n != 0 || !errors.Is(err, errTest) {
t.Fatal(n, err)
}
if p.alloc != 1 || p.free != 1 {
t.Fatal(p.alloc, p.free)
}
})
})
t.Run("writer", func(t *testing.T) {
t.Run("uninitialized writer", func(t *testing.T) {
t.Run("no writer", func(t *testing.T) {
w := buffer.BufferedWriter(nil, buffer.Options{})
if n, err := w.Write([]byte("123")); n != 0 || err == nil {
t.Fatal(n, err)
}
})
t.Run("write", func(t *testing.T) {
var w buffer.Writer
if n, err := w.Write([]byte("123")); n != 0 || err == nil {
t.Fatal(n, err)
}
})
t.Run("read from", func(t *testing.T) {
var w buffer.Writer
r := bytes.NewBuffer([]byte{1, 2, 3})
if n, err := w.ReadFrom(r); n != 0 || err == nil {
t.Fatal(n, err)
}
})
t.Run("flush", func(t *testing.T) {
var w buffer.Writer
if err := w.Flush(); err != nil {
t.Fatal(err)
}
})
t.Run("close", func(t *testing.T) {
var w buffer.Writer
if err := w.Close(); err != nil {
t.Fatal(err)
}
})
})
t.Run("default pool", func(t *testing.T) {
w := &writer{}
b := buffer.BufferedWriter(w, buffer.Options{})
if n, err := b.Write([]byte("123")); n != 3 || err != nil {
t.Fatal(n, err)
}
if err := b.Close(); err != nil {
t.Fatal(err)
}
if string(w.written) != "123" {
t.Fatal(string(w.written))
}
})
})
t.Run("closing", func(t *testing.T) {
t.Run("double closing reader", func(t *testing.T) {
g := &gen{max: 1 << 12}
p := &fakePool{allocSize: 1 << 9}
r := buffer.BufferedReader(g, buffer.Options{Pool: p})
b := bytes.NewBuffer(nil)
if n, err := io.Copy(b, r); n != 1<<12 || err != nil {
t.Fatal(n, err)
}
r.Close()
r.Close()
if p.alloc != 1 && p.free != 1 {
t.Fatal(p.alloc, p.free)
}
})
t.Run("double closing content reader", func(t *testing.T) {
c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
var n int64
for i := 0; i < 3; i++ {
ni, err := w.Write([]byte("123456789")[i*3 : i*3+3])
n += int64(ni)
if err != nil {
return n, err
}
}
return n, nil
})
p := &fakePool{allocSize: 1 << 9}
r := buffer.BufferedContent(c, buffer.Options{Pool: p})
b := bytes.NewBuffer(nil)
if n, err := io.Copy(b, r); n != 9 || err != nil {
t.Fatal(n, err)
}
r.Close()
r.Close()
if p.alloc != 1 && p.free != 1 {
t.Fatal(p.alloc, p.free)
}
})
t.Run("double closing content reader before eof", func(t *testing.T) {
c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
var n int64
for i := 0; i < 3; i++ {
ni, err := w.Write([]byte("123456789")[i*3 : i*3+3])
n += int64(ni)
if err != nil {
return n, err
}
}
return n, nil
})
p := &fakePool{allocSize: 1 << 9}
r := buffer.BufferedContent(c, buffer.Options{Pool: p})
b := make([]byte, 3)
if n, err := r.Read(b); n != 3 || err != nil || string(b) != "123" {
t.Fatal(n, err, string(b))
}
r.Close()
r.Close()
if p.alloc != 1 && p.free != 1 {
t.Fatal(p.alloc, p.free)
}
})
t.Run("double closing writer", func(t *testing.T) {
w := &writer{}
r := &gen{max: 1 << 12}
p := &fakePool{allocSize: 1 << 9}
o := buffer.Options{Pool: p}
b := buffer.BufferedWriter(w, o)
n, err := b.ReadFrom(r)
if n != 1<<12 || err != nil {
t.Fatal(n, err)
}
if err := b.Close(); err != nil {
t.Fatal(err)
}
if err := b.Close(); err != nil {
t.Fatal(err)
}
if p.alloc != 1 || p.free != 1 {
t.Fatal(p.alloc, p.free)
}
})
t.Run("closing writer after read error in read from", func(t *testing.T) {
w := &writer{}
r := &gen{
max: 1 << 12,
errAfter: []int{1 << 11},
}
p := &fakePool{allocSize: 1 << 9}
o := buffer.Options{Pool: p}
b := buffer.BufferedWriter(w, o)
n, err := b.ReadFrom(r)
if n != 1<<11 || !errors.Is(err, errTest) {
t.Fatal(n, err)
}
if err := b.Close(); err != nil {
t.Fatal(err)
}
if err := b.Close(); err != nil {
t.Fatal(err)
}
if p.alloc != 1 || p.free != 1 {
t.Fatal(p.alloc, p.free)
}
})
})
}
// -- bench
type readerOnly struct {
in io.Reader
}
type writerOnly struct {
in io.Writer
}
func (r readerOnly) Read(p []byte) (int, error) {
return r.in.Read(p)
}
func (w writerOnly) Write(p []byte) (int, error) {
return w.in.Write(p)
}
func TestBenchmarkThroughput(t *testing.T) {
p := buffer.NoPool(0)
wo := writerOnly{io.Discard}
src := &gen{max: 1 << 18}
r := buffer.BufferedReader(src, buffer.Options{Pool: p})
ro := readerOnly{r}
n, err := io.Copy(wo, ro)
if n != 1<<18 || err != nil {
t.Fatal(n, err)
}
}
func TestBenchmarkThroughputCompare(t *testing.T) {
wo := writerOnly{io.Discard}
src := &gen{max: 1 << 18}
r := bufio.NewReader(src)
ro := readerOnly{r}
n, err := io.Copy(wo, ro)
if n != 1<<18 || err != nil {
t.Fatal(n, err)
}
}
func BenchmarkThroughput(b *testing.B) {
p := buffer.NoPool(0)
wo := writerOnly{io.Discard}
for i := 0; i < b.N; i++ {
src := &gen{max: 1 << 18}
r := buffer.BufferedReader(src, buffer.Options{Pool: p})
ro := readerOnly{r}
io.Copy(wo, ro)
}
}
func BenchmarkThroughputCompare(b *testing.B) {
wo := writerOnly{io.Discard}
for i := 0; i < b.N; i++ {
src := &gen{max: 1 << 18}
r := bufio.NewReader(src)
ro := readerOnly{r}
io.Copy(wo, ro)
}
}
func TestBenchmarkThroughputPooled(t *testing.T) {
p := &foreverPool{allocSize: 1 << 12}
wo := writerOnly{io.Discard}
src := &gen{max: 1 << 18}
r := buffer.BufferedReader(src, buffer.Options{Pool: p})
ro := readerOnly{r}
n, err := io.Copy(wo, ro)
if n != 1<<18 || err != nil {
t.Fatal(n, err)
}
}
func TestBenchmarkThroughputPooledCompare(t *testing.T) {
wo := writerOnly{io.Discard}
r := bufio.NewReader(nil)
src := &gen{max: 1 << 18}
r.Reset(src)
ro := readerOnly{r}
n, err := io.Copy(wo, ro)
if n != 1<<18 || err != nil {
t.Fatal(n, err)
}
}
func BenchmarkThroughputPooled(b *testing.B) {
p := &foreverPool{allocSize: 1 << 12}
wo := writerOnly{io.Discard}
for i := 0; i < b.N; i++ {
src := &gen{max: 1 << 18}
r := buffer.BufferedReader(src, buffer.Options{Pool: p})
ro := readerOnly{r}
io.Copy(wo, ro)
}
}
func BenchmarkThroughputPooledCompare(b *testing.B) {
wo := writerOnly{io.Discard}
r := bufio.NewReader(nil)
for i := 0; i < b.N; i++ {
src := &gen{max: 1 << 18}
r.Reset(src)
ro := readerOnly{r}
io.Copy(wo, ro)
}
}
func TestBenchmarkThroughputPooledParallel(t *testing.T) {
p := newSyncedForeverPool(func() []byte {
return make([]byte, 1<<12)
})
wo := writerOnly{io.Discard}
src := &gen{max: 1 << 18}
r := buffer.BufferedReader(src, buffer.Options{Pool: p})
ro := readerOnly{r}
n, err := io.Copy(wo, ro)
if n != 1<<18 || err != nil {
t.Fatal(n, err)
}
}
func TestBenchmarkThroughputPooledParallelCompare(t *testing.T) {
p := newSyncedForeverPool(func() *bufio.Reader {
return bufio.NewReader(nil)
})
wo := writerOnly{io.Discard}
r, err := p.Get()
if err != nil {
t.Fatal(err)
}
src := &gen{max: 1 << 18}
r.Reset(src)
ro := readerOnly{r}
n, err := io.Copy(wo, ro)
if n != 1<<18 || err != nil {
t.Fatal(n, err)
}
}
func BenchmarkThroughputPooledParallel(b *testing.B) {
p := newSyncedForeverPool(func() []byte {
return make([]byte, 1<<12)
})
b.ResetTimer()
b.SetParallelism(128)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
src := &gen{max: 1 << 18}
r := buffer.BufferedReader(src, buffer.Options{Pool: p})
ro := readerOnly{r}
wo := writerOnly{io.Discard}
io.Copy(wo, ro)
}
})
}
func BenchmarkThroughputPooledParallelCompare(b *testing.B) {
p := newSyncedForeverPool(func() *bufio.Reader {
return bufio.NewReader(nil)
})
b.ResetTimer()
b.SetParallelism(128)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
r, _ := p.Get()
src := &gen{max: 1 << 18}
r.Reset(src)
ro := readerOnly{r}
wo := writerOnly{io.Discard}
io.Copy(wo, ro)
}
})
}
func TestBenchmarkScan(t *testing.T) {
const delimiterPosition = 1<<17 + 1<<16 - 3
p := buffer.NoPool(0)
src := &gen{
max: 1 << 18,
customContentAfter: []int{delimiterPosition},
customContent: map[int][]byte{delimiterPosition: []byte("123")},
}
r := buffer.BufferedReader(src, buffer.Options{Pool: p})
b, ok, err := r.ReadBytes([]byte{'1'}, 1<<18)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("delimiter")
}
if !bytes.Equal(b, append(generate(delimiterPosition), '1')) {
t.Fatal("content")
}
}
func TestBenchmarkScanCompare(t *testing.T) {
const delimiterPosition = 1<<17 + 1<<16 - 3
src := &gen{
max: 1 << 18,
customContentAfter: []int{delimiterPosition},
customContent: map[int][]byte{delimiterPosition: []byte("123")},
}
r := bufio.NewReader(src)
b, err := r.ReadBytes('1')
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, append(generate(delimiterPosition), '1')) {
t.Fatal("content")
}
}
func BenchmarkScan(b *testing.B) {
const delimiterPosition = 1<<17 + 1<<16 - 3
p := buffer.NoPool(0)
for i := 0; i < b.N; i++ {
src := &gen{
max: 1 << 18,
customContentAfter: []int{delimiterPosition},
customContent: map[int][]byte{delimiterPosition: []byte("123")},
}
r := buffer.BufferedReader(src, buffer.Options{Pool: p})
r.ReadBytes([]byte{'1'}, 1<<18)
}
}
func BenchmarkScanCompare(b *testing.B) {
const delimiterPosition = 1<<17 + 1<<16 - 3
for i := 0; i < b.N; i++ {
src := &gen{
max: 1 << 18,
customContentAfter: []int{delimiterPosition},
customContent: map[int][]byte{delimiterPosition: []byte("123")},
}
r := bufio.NewReader(src)
r.ReadBytes('1')
}
}
func TestBenchmarkScanPooled(t *testing.T) {
const delimiterPosition = 1<<17 + 1<<16 - 3
p := &foreverPool{allocSize: 1 << 12}
src := &gen{
max: 1 << 18,
customContentAfter: []int{delimiterPosition},
customContent: map[int][]byte{delimiterPosition: []byte("123")},
}
r := buffer.BufferedReader(src, buffer.Options{Pool: p})
b, ok, err := r.ReadBytes([]byte{'1'}, 1<<18)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("delimiter")
}
if !bytes.Equal(b, append(generate(delimiterPosition), '1')) {
t.Fatal("content")
}
}
func TestBenchmarkScanPooledCompare(t *testing.T) {
const delimiterPosition = 1<<17 + 1<<16 - 3
src := &gen{
max: 1 << 18,
customContentAfter: []int{delimiterPosition},
customContent: map[int][]byte{delimiterPosition: []byte("123")},
}
r := bufio.NewReader(nil)
r.Reset(src)
b, err := r.ReadBytes('1')
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, append(generate(delimiterPosition), '1')) {
t.Fatal("content")
}
}
func BenchmarkScanPooled(b *testing.B) {
const delimiterPosition = 1<<17 + 1<<16 - 3
p := &foreverPool{allocSize: 1 << 12}
for i := 0; i < b.N; i++ {
src := &gen{
max: 1 << 18,
customContentAfter: []int{delimiterPosition},
customContent: map[int][]byte{delimiterPosition: []byte("123")},
}
r := buffer.BufferedReader(src, buffer.Options{Pool: p})
r.ReadBytes([]byte{'1'}, 1<<18)
}
}
func BenchmarkScanPooledCompare(b *testing.B) {
const delimiterPosition = 1<<17 + 1<<16 - 3
r := bufio.NewReader(nil)
for i := 0; i < b.N; i++ {
src := &gen{
max: 1 << 18,
customContentAfter: []int{delimiterPosition},
customContent: map[int][]byte{delimiterPosition: []byte("123")},
}
r.Reset(src)
r.ReadBytes('1')
}
}
func TestBenchmarkScanPooledParallel(t *testing.T) {
const delimiterPosition = 1<<17 + 1<<16 - 3
p := newSyncedForeverPool(func() []byte {
return make([]byte, 1<<12)
})
src := &gen{
max: 1 << 18,
customContentAfter: []int{delimiterPosition},
customContent: map[int][]byte{delimiterPosition: []byte("123")},
}
r := buffer.BufferedReader(src, buffer.Options{Pool: p})
b, ok, err := r.ReadBytes([]byte{'1'}, 1<<18)
if err != nil {
t.Fatal(err)
}
if !ok {
t.Fatal("delimiter")
}
if !bytes.Equal(b, append(generate(delimiterPosition), '1')) {
t.Fatal("content")
}
}
func TestBenchmarkScanPooledParallelCompare(t *testing.T) {
const delimiterPosition = 1<<17 + 1<<16 - 3
p := newSyncedForeverPool(func() *bufio.Reader {
return bufio.NewReader(nil)
})
src := &gen{
max: 1 << 18,
customContentAfter: []int{delimiterPosition},
customContent: map[int][]byte{delimiterPosition: []byte("123")},
}
r, err := p.Get()
if err != nil {
t.Fatal(err)
}
r.Reset(src)
b, err := r.ReadBytes('1')
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, append(generate(delimiterPosition), '1')) {
t.Fatal("content")
}
}
func BenchmarkScanPooledParallel(b *testing.B) {
const delimiterPosition = 1<<17 + 1<<16 - 3
p := newSyncedForeverPool(func() []byte {
return make([]byte, 1<<12)
})
b.ResetTimer()
b.SetParallelism(128)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
src := &gen{
max: 1 << 18,
customContentAfter: []int{delimiterPosition},
customContent: map[int][]byte{delimiterPosition: []byte("123")},
}
r := buffer.BufferedReader(src, buffer.Options{Pool: p})
r.ReadBytes([]byte{'1'}, 1<<18)
}
})
}
func BenchmarkScanPooledParallelCompare(b *testing.B) {
const delimiterPosition = 1<<17 + 1<<16 - 3
p := newSyncedForeverPool(func() *bufio.Reader {
return bufio.NewReader(nil)
})
b.ResetTimer()
b.SetParallelism(128)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
src := &gen{
max: 1 << 18,
customContentAfter: []int{delimiterPosition},
customContent: map[int][]byte{delimiterPosition: []byte("123")},
}
r, _ := p.Get()
r.Reset(src)
r.ReadBytes('1')
}
})
}