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) } }) }) } // -- 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) dst := bytes.NewBuffer(nil) wo := writerOnly{dst} 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) { dst := bytes.NewBuffer(nil) wo := writerOnly{dst} 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) dst := bytes.NewBuffer(nil) wo := writerOnly{dst} for i := 0; i < b.N; i++ { src := &gen{max: 1 << 18} r := buffer.BufferedReader(src, buffer.Options{Pool: p}) ro := readerOnly{r} dst.Reset() io.Copy(wo, ro) } } func BenchmarkThroughputCompare(b *testing.B) { dst := bytes.NewBuffer(nil) wo := writerOnly{dst} for i := 0; i < b.N; i++ { src := &gen{max: 1 << 18} r := bufio.NewReader(src) ro := readerOnly{r} dst.Reset() io.Copy(wo, ro) } } func TestThroughputPooled(t *testing.T) { p := &foreverPool{allocSize: 1 << 12} dst := bytes.NewBuffer(nil) wo := writerOnly{dst} 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 TestThroughputPooledCompare(t *testing.T) { dst := bytes.NewBuffer(nil) wo := writerOnly{dst} 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} dst := bytes.NewBuffer(nil) wo := writerOnly{dst} for i := 0; i < b.N; i++ { src := &gen{max: 1 << 18} r := buffer.BufferedReader(src, buffer.Options{Pool: p}) ro := readerOnly{r} dst.Reset() io.Copy(wo, ro) } } func BenchmarkThroughputPooledCompare(b *testing.B) { dst := bytes.NewBuffer(nil) wo := writerOnly{dst} r := bufio.NewReader(nil) for i := 0; i < b.N; i++ { src := &gen{max: 1 << 18} r.Reset(src) ro := readerOnly{r} dst.Reset() io.Copy(wo, ro) } } func TestThroughputPooledParallel(t *testing.T) { p := newSyncedForeverPool(func() []byte { return make([]byte, 1<<12) }) dst := bytes.NewBuffer(nil) wo := writerOnly{dst} 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 TestThroughputPooledParallelCompare(t *testing.T) { p := newSyncedForeverPool(func() *bufio.Reader { return bufio.NewReader(nil) }) dst := bytes.NewBuffer(nil) wo := writerOnly{dst} 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.RunParallel(func(pb *testing.PB) { for pb.Next() { src := &gen{max: 1 << 18} r := buffer.BufferedReader(src, buffer.Options{Pool: p}) ro := readerOnly{r} dst := bytes.NewBuffer(nil) wo := writerOnly{dst} io.Copy(wo, ro) } }) } func BenchmarkThroughputPooledParallelCompare(b *testing.B) { p := newSyncedForeverPool(func() *bufio.Reader { return bufio.NewReader(nil) }) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { r, _ := p.Get() src := &gen{max: 1 << 18} r.Reset(src) ro := readerOnly{r} dst := bytes.NewBuffer(nil) wo := writerOnly{dst} io.Copy(wo, ro) } }) }