1
0

content reader and testing

This commit is contained in:
Arpad Ryszka 2026-02-22 16:30:37 +01:00
parent 3c238b5248
commit 83801867f4
14 changed files with 2901 additions and 2219 deletions

View File

@ -1,172 +1,151 @@
package buffer_test package buffer_test
import ( import (
"testing"
"code.squareroundforest.org/arpio/buffer"
"bytes" "bytes"
"code.squareroundforest.org/arpio/buffer"
"testing"
) )
func TestBuffered(t *testing.T) { func TestBuffered(t *testing.T) {
t.Run("none buffered", func(t *testing.T) { for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} {
g := &gen{max: 1 << 15} t.Run(title, func(t *testing.T) {
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} t.Run("none buffered", func(t *testing.T) {
r := buffer.ReaderFrom(g, o) g := &gen{max: 1 << 15}
b := r.Buffered() o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
if len(b) != 0 { r := cr(g, o)
t.Fatal("invalid content") b := r.Buffered()
} if len(b) != 0 {
}) t.Fatal("invalid content")
}
})
t.Run("buffered after error", func(t *testing.T) { t.Run("buffered after error", func(t *testing.T) {
g := &gen{ g := &gen{
max: 12, max: 12,
fastErr: true, fastErr: true,
} }
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := buffer.ReaderFrom(g, o) r := cr(g, o)
b, err := r.Peek(18) b, err := r.Peek(18)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !bytes.Equal(b, generate(12)) { if !bytes.Equal(b, generate(12)) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
b = r.Buffered() b = r.Buffered()
if !bytes.Equal(b, generate(12)) { if !bytes.Equal(b, generate(12)) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
}) })
t.Run("all buffered", func(t *testing.T) { t.Run("all buffered", func(t *testing.T) {
g := &gen{max: 1 << 15} g := &gen{max: 1 << 15}
o := buffer.Options{ o := buffer.Options{Pool: buffer.NoPool(64)}
Pool: buffer.NoPool(64), r := cr(g, o)
ReadSize: 16, b, err := r.Peek(48)
} if err != nil {
t.Fatal(err)
}
r := buffer.ReaderFrom(g, o) if !bytes.Equal(b, generate(48)) {
b, err := r.Peek(48) t.Fatal("invalid content 1", len(b))
if err != nil { }
t.Fatal(err)
}
if !bytes.Equal(b, generate(48)) { b = r.Buffered()
t.Fatal("invalid content 1", len(b)) if !bytes.Equal(b, generate(64)) {
} t.Fatal("invalid content 2", len(b))
}
})
b = r.Buffered() t.Run("buffered across segments", func(t *testing.T) {
if !bytes.Equal(b, generate(48)) { g := &gen{max: 1 << 15}
t.Fatal("invalid content 2", len(b)) o := buffer.Options{Pool: buffer.NoPool(64)}
} r := cr(g, o)
}) b, err := r.Peek(144)
if err != nil {
t.Fatal(err)
}
t.Run("buffered across segments", func(t *testing.T) { if !bytes.Equal(b, generate(144)) {
g := &gen{max: 1 << 15} t.Fatal("invalid content 1", len(b))
o := buffer.Options{ }
Pool: buffer.NoPool(64),
ReadSize: 16,
}
r := buffer.ReaderFrom(g, o) b = r.Buffered()
b, err := r.Peek(144) if !bytes.Equal(b, generate(192)) {
if err != nil { t.Fatal("invalid content 2", len(b))
t.Fatal(err) }
} })
if !bytes.Equal(b, generate(144)) { t.Run("buffered mid segment", func(t *testing.T) {
t.Fatal("invalid content 1", len(b)) g := &gen{max: 1 << 15}
} o := buffer.Options{Pool: buffer.NoPool(128)}
r := cr(g, o)
b := make([]byte, 32)
n, err := r.Read(b)
if err != nil {
t.Fatal(err)
}
b = r.Buffered() if n != 32 {
if !bytes.Equal(b, generate(192)) { t.Fatal("invalid read length", n)
t.Fatal("invalid content 2", len(b)) }
}
})
t.Run("buffered mid segment", func(t *testing.T) { if !bytes.Equal(b, generate(32)) {
g := &gen{max: 1 << 15} t.Fatal("invalid content 1")
o := buffer.Options{ }
Pool: buffer.NoPool(128),
ReadSize: 64,
}
r := buffer.ReaderFrom(g, o) b = r.Buffered()
b := make([]byte, 32) if !bytes.Equal(b, generate(128)[32:]) {
n, err := r.Read(b) t.Fatal("invalid content 2")
if err != nil { }
t.Fatal(err) })
}
if n != 32 { t.Run("buffered mid segment across segments", func(t *testing.T) {
t.Fatal("invalid read length", n) g := &gen{max: 1 << 15}
} o := buffer.Options{Pool: buffer.NoPool(128)}
r := cr(g, o)
b, err := r.Peek(288)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, generate(32)) { if !bytes.Equal(b, generate(288)) {
t.Fatal("invalid content") t.Fatal("invalid content 1")
} }
b = r.Buffered() b = r.Buffered()
if !bytes.Equal(b, generate(64)[32:]) { if !bytes.Equal(b, generate(384)) {
t.Fatal("invalid content") t.Fatal("invalid content 2", len(b))
} }
}) })
t.Run("buffered mid segment across segments", func(t *testing.T) { t.Run("zero buffered mid segment", func(t *testing.T) {
g := &gen{max: 1 << 15} g := &gen{max: 1 << 15}
o := buffer.Options{ o := buffer.Options{Pool: buffer.NoPool(64)}
Pool: buffer.NoPool(128), r := cr(g, o)
ReadSize: 64, b := make([]byte, 64)
} n, err := r.Read(b)
if err != nil {
t.Fatal(err)
}
r := buffer.ReaderFrom(g, o) if n != 64 {
b := make([]byte, 288) t.Fatal("invalid read length", n)
n, err := r.Read(b) }
if err != nil {
t.Fatal(err)
}
if n != 288 { if !bytes.Equal(b, generate(64)) {
t.Fatal("invalid read length", n) t.Fatal("invalid content 1")
} }
if !bytes.Equal(b, generate(288)) { b = r.Buffered()
t.Fatal("invalid content 1") if len(b) != 0 {
} t.Fatal("invalid content 2", len(b))
}
b = r.Buffered() })
if !bytes.Equal(b, generate(384)[288:]) { })
t.Fatal("invalid content 2", len(b)) }
}
})
t.Run("zero buffered mid segment", func(t *testing.T) {
g := &gen{max: 1 << 15}
o := buffer.Options{
Pool: buffer.NoPool(128),
ReadSize: 64,
}
r := buffer.ReaderFrom(g, o)
b := make([]byte, 64)
n, err := r.Read(b)
if err != nil {
t.Fatal(err)
}
if n != 64 {
t.Fatal("invalid read length", n)
}
if !bytes.Equal(b, generate(64)) {
t.Fatal("invalid content 1")
}
b = r.Buffered()
if len(b) != 0 {
t.Fatal("invalid content 2", len(b))
}
})
} }

120
content.go Normal file
View File

@ -0,0 +1,120 @@
package buffer
// TODO:
// - the write internally can get a max in the request, and this way multiple writes can be executed without
// synchronization steps in between
// - the current segment can be passed in to the writer to copy to
// - additional segments can be requested from the writer if necessary
// - a single channel with buffer=1 can be used to exchange
// - the message can be called 'syncMessage'
// - filter out the errors coming from the writer, but originated from the reader. Can errors.Is used for this?
// If yes, replace the captured error
// - check if the existing stdlib interface is good for the content writer
import (
"errors"
"io"
)
type syncMessage struct {
p []byte
n int
err error
}
type contentWriter struct {
w chan<- syncMessage
r <-chan syncMessage
}
type content struct {
wrt io.WriterTo
started bool
w chan syncMessage
r chan syncMessage
}
func mkcontent(wrt io.WriterTo) *content {
return &content{
wrt: wrt,
w: make(chan syncMessage, 1),
r: make(chan syncMessage, 1),
}
}
func (w contentWriter) Write(p []byte) (int, error) {
var n int
for {
if len(p) == 0 {
return n, nil
}
sm, ok := <-w.r
if !ok {
return n, ErrContentAbort
}
ni := copy(sm.p, p)
n += ni
p = p[ni:]
w.w <- syncMessage{n: ni}
}
}
func (c *content) writeTo() {
w := contentWriter{
w: c.r,
r: c.w,
}
var sm syncMessage
_, err := c.wrt.WriteTo(w)
if err != nil {
sm = syncMessage{err: err}
}
if err == nil {
sm = syncMessage{err: io.EOF}
}
w.w <- sm
close(w.w)
}
func (c *content) Read(p []byte) (int, error) {
if !c.started {
go c.writeTo()
c.started = true
}
c.w <- syncMessage{p: p}
sm := <-c.r
return sm.n, sm.err
}
func (c *content) close() error {
if !c.started {
return nil
}
// there can be only zero or one messages to receive, but we need to wait for the channel be closed by
// writeTo:
var errs []error
close(c.w)
for sm := range c.r {
// not using errors.Is, because the writer logic may have combined the abort error with another
// user logic error, that needs to be reported:
if sm.err == ErrContentAbort {
continue
}
if sm.err == io.EOF {
continue
}
errs = append(errs, sm.err)
}
return errors.Join(errs...)
}

341
content_test.go Normal file
View File

@ -0,0 +1,341 @@
package buffer
import (
"testing"
"io"
"errors"
)
type testPool struct {
size int
failAfter []int
count int
}
var (
errTest = errors.New("test error")
errTest2 = errors.New("test error 2")
)
func (p *testPool) Get() ([]byte, error) {
defer func() {
p.count++
}()
if len(p.failAfter) > 0 && p.count == p.failAfter[0] {
p.failAfter = p.failAfter[1:]
return nil, errTest
}
return make([]byte, p.size), nil
}
func (p *testPool) Put([]byte) {}
func TestContent(t *testing.T) {
t.Run("eof", func(t *testing.T) {
c := 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 := &testPool{size: 2}
o := Options{Pool: p}
r := 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, io.EOF) {
t.Fatal(n, err)
}
})
t.Run("eof right away", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) {
return 0, nil
})
p := &testPool{size: 2}
o := Options{Pool: p}
r := BufferedContent(c, o)
b := make([]byte, 3)
n, err := r.Read(b)
if n != 0 || !errors.Is(err, io.EOF) {
t.Fatal(n, err)
}
})
t.Run("writer error", func(t *testing.T) {
c := 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 := &testPool{size: 2}
o := Options{Pool: p}
r := 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)
}
})
t.Run("writer error right away", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) {
return 0, errTest
})
p := &testPool{size: 2}
o := Options{Pool: p}
r := BufferedContent(c, o)
b := make([]byte, 3)
n, err := r.Read(b)
if n != 0 || !errors.Is(err, errTest) {
t.Fatal(n, err)
}
})
t.Run("abort", func(t *testing.T) {
c := 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 := &testPool{
size: 2,
failAfter: []int{1},
}
o := Options{Pool: p}
r := BufferedContent(c, o)
b, ok, err := r.ReadBytes([]byte("67"), 12)
if string(b) != "12" /* segment size og 2 by the pool */ || ok || err != nil {
t.Fatal(string(b), ok, err)
}
b, ok, err = r.ReadBytes([]byte("67"), 12)
if len(b) != 0 || ok || !errors.Is(err, errTest) {
t.Fatal(string(b), ok, err)
}
})
t.Run("abort right away", func(t *testing.T) {
c := 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 := &testPool{
size: 2,
failAfter: []int{0},
}
o := Options{Pool: p}
r := BufferedContent(c, o)
b, ok, err := r.ReadBytes([]byte("67"), 12)
if len(b) != 0 || ok || !errors.Is(err, errTest) {
t.Fatal(string(b), ok, err)
}
})
t.Run("close when implementation ignores writer errors", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) {
w.Write([]byte("123"))
w.Write([]byte("456"))
w.Write([]byte("123"))
return 0, nil
})
p := &testPool{
size: 2,
failAfter: []int{1},
}
o := Options{Pool: p}
r := BufferedContent(c, o)
b, ok, err := r.ReadBytes([]byte("67"), 12)
if string(b) != "12" /* segment size og 2 by the pool */ || ok || err != nil {
t.Fatal(string(b), ok, err)
}
b, ok, err = r.ReadBytes([]byte("67"), 12)
if len(b) != 0 || ok || !errors.Is(err, errTest) {
t.Fatal(string(b), ok, err)
}
})
t.Run("zero write", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) {
w.Write([]byte("123"))
w.Write(nil)
w.Write([]byte("456"))
w.Write([]byte("789"))
return 0, nil
})
p := &testPool{size: 2}
o := Options{Pool: p}
r := 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, io.EOF) {
t.Fatal(n, err)
}
})
t.Run("zero write right away", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) {
w.Write(nil)
w.Write([]byte("123"))
w.Write([]byte("456"))
w.Write([]byte("789"))
return 0, nil
})
p := &testPool{size: 2}
o := Options{Pool: p}
r := 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, io.EOF) {
t.Fatal(n, err)
}
})
t.Run("custom error", func(t *testing.T) {
c := 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 := &testPool{size: 3}
o := Options{Pool: p}
r := 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)
}
})
t.Run("custom error with pool error", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) {
w.Write([]byte("123"))
w.Write([]byte("456"))
w.Write([]byte("123"))
return 0, errTest2
})
p := &testPool{
size: 2,
failAfter: []int{1},
}
o := Options{Pool: p}
r := BufferedContent(c, o)
b, ok, err := r.ReadBytes([]byte("67"), 12)
if string(b) != "12" /* segment size og 2 by the pool */ || ok || err != nil {
t.Fatal(string(b), ok, err)
}
b, ok, err = r.ReadBytes([]byte("67"), 12)
if len(b) != 0 || ok || !errors.Is(err, errTest) || !errors.Is(err, errTest2) {
t.Fatal(string(b), ok, err)
}
})
}

0
go.sum Normal file
View File

View File

@ -1,10 +1,13 @@
package buffer_test package buffer_test
import ( import (
"code.squareroundforest.org/arpio/buffer"
"errors" "errors"
"io" "io"
) )
type createReader func(r io.Reader, o buffer.Options) buffer.Reader
type gen struct { type gen struct {
rng []byte rng []byte
max int max int
@ -17,8 +20,8 @@ type gen struct {
} }
type writer struct { type writer struct {
written []byte written []byte
errAfter []int errAfter []int
shortAfter []int shortAfter []int
} }
@ -135,7 +138,7 @@ func (w *writer) Write(p []byte) (int, error) {
if len(p) > 0 && len(w.shortAfter) > 0 && len(w.written) >= w.shortAfter[0] { if len(p) > 0 && len(w.shortAfter) > 0 && len(w.written) >= w.shortAfter[0] {
w.shortAfter = w.shortAfter[1:] w.shortAfter = w.shortAfter[1:]
p = p[:len(p) / 2] p = p[:len(p)/2]
} }
wp := make([]byte, len(p)) wp := make([]byte, len(p))
@ -143,3 +146,12 @@ func (w *writer) Write(p []byte) (int, error) {
w.written = append(w.written, wp...) w.written = append(w.written, wp...)
return len(p), nil return len(p), nil
} }
func testContent(r io.Reader, o buffer.Options) buffer.Reader {
return buffer.BufferedContent(
buffer.ContentFunc(func(w io.Writer) (int64, error) {
return io.Copy(w, r)
}),
o,
)
}

75
lib.go
View File

@ -3,8 +3,8 @@
package buffer package buffer
import ( import (
"io"
"errors" "errors"
"io"
) )
type Pool interface { type Pool interface {
@ -12,20 +12,14 @@ type Pool interface {
Put([]byte) Put([]byte)
} }
type ContentWriter interface {
WriteTo(io.WriteCloser) (int, error)
}
type Options struct { type Options struct {
// defaults to new instance created by DefaultPool() // defaults to new instance created by DefaultPool()
Pool Pool Pool Pool
// may differ, default 512
ReadSize int
} }
// initialize with NewReader or ReaderFrom type ContentFunc func(io.Writer) (int64, error)
// uninitialized reader panics // uninitialized reader panics
// reads from the underlying reader until the first error, but only returns an error when the buffer is empty // reads from the underlying reader until the first error, but only returns an error when the buffer is empty
// once the underlying reader returned an error, it doesn't attempt to read from it anymore // once the underlying reader returned an error, it doesn't attempt to read from it anymore
@ -34,7 +28,10 @@ type Reader struct {
reader *reader reader *reader
} }
var ErrZeroAllocation = errors.New("zero allocation") var (
ErrZeroAllocation = errors.New("zero allocation")
ErrContentAbort = errors.New("content pipe aborted")
)
func DefaultPool() Pool { func DefaultPool() Pool {
return newPool() return newPool()
@ -44,26 +41,49 @@ func NoPool(allocSize int) Pool {
return noPool{allocSize: allocSize} return noPool{allocSize: allocSize}
} }
func ReaderFrom(in io.Reader, o Options) Reader { func (f ContentFunc) WriteTo(w io.Writer) (int64, error) {
if o.Pool == nil { return f(w)
o.Pool = DefaultPool() }
// similar to bufio.Reader, but with pooled buffering internally
func BufferedReader(in io.Reader, o Options) Reader {
if in == nil {
return Reader{}
} }
if o.ReadSize <= 0 { if o.Pool == nil {
o.ReadSize = 1 << 9 o.Pool = DefaultPool()
} }
return Reader{reader: &reader{options: o, in: in}} return Reader{reader: &reader{options: o, in: in}}
} }
func ReaderFromContent(w ContentWriter, o Options) Reader { // similar to a pipe, but with dynamic and pooled buffering internally
return Reader{} // the individual Write calls are blocked until the reading end requests more data
// WriterTo instances need to be safe to call in goroutines other than they were created in
// if it returns with nil error, it will be interpreted as EOF on the reader side
// unfinished calls to the passed in io.Writer will return with ErrContentAbort when the buffer
// needs to be consumed
func BufferedContent(c io.WriterTo, o Options) Reader {
if c == nil {
return Reader{}
}
if o.Pool == nil {
o.Pool = DefaultPool()
}
return Reader{reader: &reader{options: o, in: mkcontent(c)}}
} }
// - it returns an error only when the underlying reader returned an error and the internal buffer is empty // - it returns an error only when the underlying reader returned an error and the internal buffer is empty
// - if the underlying reader returns zero read length and nil error, and the buffer is empty, it returns zero // - if the underlying reader returns zero read length and nil error, and the buffer is empty, it returns zero
// read length and nil error. It's up the calling code to decide how to proceed in such cases // read length and nil error. It's up the calling code to decide how to proceed in such cases
func (r Reader) Read(p []byte) (int, error) { func (r Reader) Read(p []byte) (int, error) {
if r.reader == nil {
return 0, io.EOF
}
return r.reader.read(p) return r.reader.read(p)
} }
@ -79,26 +99,47 @@ func (r Reader) Read(p []byte) (int, error) {
// - when the buffer is empty, and the underlying reader previously returned an error, it returns zero length // - when the buffer is empty, and the underlying reader previously returned an error, it returns zero length
// bytes, false, and the error // bytes, false, and the error
func (r Reader) ReadBytes(delimiter []byte, max int) ([]byte, bool, error) { func (r Reader) ReadBytes(delimiter []byte, max int) ([]byte, bool, error) {
if r.reader == nil {
return nil, false, io.EOF
}
return r.reader.readBytes(delimiter, max) return r.reader.readBytes(delimiter, max)
} }
// only returns an error when the underlying reader returned an error, or the used pool returned an error // only returns an error when the underlying reader returned an error, or the used pool returned an error
func (r Reader) ReadUTF8(max int) ([]rune, int, error) { func (r Reader) ReadUTF8(max int) ([]rune, int, error) {
if r.reader == nil {
return nil, 0, io.EOF
}
return r.reader.readUTF8(max) return r.reader.readUTF8(max)
} }
// not a copy // not a copy
// error only when nothing buffered
func (r Reader) Peek(max int) ([]byte, error) { func (r Reader) Peek(max int) ([]byte, error) {
if r.reader == nil {
return nil, io.EOF
}
return r.reader.peek(max) return r.reader.peek(max)
} }
// not a copy // not a copy
// can be wrong after error // can be wrong after error
func (r Reader) Buffered() []byte { func (r Reader) Buffered() []byte {
if r.reader == nil {
return nil
}
return r.reader.buffered() return r.reader.buffered()
} }
// important that the writer must not modify the slice data, as defined in the io.Writer interface // important that the writer must not modify the slice data, as defined in the io.Writer interface
func (r Reader) WriteTo(w io.Writer) (int64, error) { func (r Reader) WriteTo(w io.Writer) (int64, error) {
if r.reader == nil {
return 0, nil
}
return r.reader.writeTo(w) return r.reader.writeTo(w)
} }

View File

@ -1,228 +1,212 @@
package buffer_test package buffer_test
import ( import (
"testing"
"code.squareroundforest.org/arpio/buffer"
"bytes" "bytes"
"io" "code.squareroundforest.org/arpio/buffer"
"errors" "errors"
"io"
"testing"
) )
func TestPeek(t *testing.T) { func TestPeek(t *testing.T) {
t.Run("peek across segments", func(t *testing.T) { for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} {
g := &gen{max: 1 << 15} t.Run(title, func(t *testing.T) {
o := buffer.Options{ t.Run("peek across segments", func(t *testing.T) {
Pool: buffer.NoPool(128), g := &gen{max: 1 << 15}
ReadSize: 8, o := buffer.Options{Pool: buffer.NoPool(128)}
} r := cr(g, o)
p, err := r.Peek(2*128 + 30)
if err != nil {
t.Fatal(err)
}
r := buffer.ReaderFrom(g, o) if !bytes.Equal(p, generate(2*128+30)) {
p, err := r.Peek(2*128 + 30) t.Fatal("failed to peek", len(p))
if err != nil { }
t.Fatal(err) })
}
if !bytes.Equal(p, generate(2*128+30)) { t.Run("err before and after consumed", func(t *testing.T) {
t.Fatal("failed to peek", len(p)) g := &gen{
} max: 12,
}) fastErr: true,
}
t.Run("err before and after consumed", func(t *testing.T) { o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
g := &gen{ r := cr(g, o)
max: 12, b, err := r.Peek(18)
fastErr: true, if err != nil {
} t.Fatal(err)
}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} if !bytes.Equal(b, generate(12)) {
r := buffer.ReaderFrom(g, o) t.Fatal("invalid content", len(b))
b, err := r.Peek(18) }
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, generate(12)) { b, err = r.Peek(18)
t.Fatal("invalid content", len(b)) if err != nil {
} t.Fatal(err)
}
b, err = r.Peek(18) if !bytes.Equal(b, generate(12)) {
if err != nil { t.Fatal("invalid content", len(b))
t.Fatal(err) }
}
if !bytes.Equal(b, generate(12)) { b, err = io.ReadAll(r)
t.Fatal("invalid content", len(b)) if err != nil {
} t.Fatal(err)
}
b, err = io.ReadAll(r) if !bytes.Equal(b, generate(12)) {
if err != nil { t.Fatal("invalid content", len(b))
t.Fatal(err) }
}
if !bytes.Equal(b, generate(12)) { b, err = r.Peek(18)
t.Fatal("invalid content", len(b)) if !errors.Is(err, io.EOF) {
} t.Fatal("failed EOF")
}
b, err = r.Peek(18) if len(b) != 0 {
if !errors.Is(err, io.EOF) { t.Fatal("invalid content", len(b))
t.Fatal("failed EOF") }
} })
if len(b) != 0 { t.Run("err immediately on first try to fill", func(t *testing.T) {
t.Fatal("invalid content", len(b)) g := &gen{}
} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
}) r := cr(g, o)
b, err := r.Peek(18)
if !errors.Is(err, io.EOF) {
t.Fatal("failed EOF")
}
t.Run("err immediately on first try to fill", func(t *testing.T) { if len(b) != 0 {
g := &gen{} t.Fatal("invalid content")
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} }
r := buffer.ReaderFrom(g, o) })
b, err := r.Peek(18)
if !errors.Is(err, io.EOF) {
t.Fatal("failed EOF")
}
if len(b) != 0 { t.Run("peek on empty", func(t *testing.T) {
t.Fatal("invalid content") g := &gen{max: 1 << 15}
} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
}) r := cr(g, o)
b, err := r.Peek(18)
if err != nil {
t.Fatal(err)
}
t.Run("peek on empty", func(t *testing.T) { if !bytes.Equal(b, generate(18)) {
g := &gen{max: 1 << 15} t.Fatal("invalid content")
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} }
r := buffer.ReaderFrom(g, o) })
b, err := r.Peek(18)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, generate(18)) { t.Run("peek multiple segments", func(t *testing.T) {
t.Fatal("invalid content") g := &gen{max: 1 << 15}
} o := buffer.Options{Pool: buffer.NoPool(64)}
}) r := cr(g, o)
b, err := r.Peek(160)
if err != nil {
t.Fatal(err)
}
t.Run("peek multiple segments", func(t *testing.T) { if !bytes.Equal(b, generate(160)) {
g := &gen{max: 1 << 15} t.Fatal("invalid content")
o := buffer.Options{ }
Pool: buffer.NoPool(64), })
ReadSize: 16,
}
r := buffer.ReaderFrom(g, o) t.Run("peek on partially filled", func(t *testing.T) {
b, err := r.Peek(160) g := &gen{max: 1 << 15}
if err != nil { o := buffer.Options{Pool: buffer.NoPool(64)}
t.Fatal(err) r := cr(g, o)
} b, err := r.Peek(16)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, generate(160)) { if !bytes.Equal(b, generate(16)) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
})
t.Run("peek on partially filled", func(t *testing.T) { b, err = r.Peek(48)
g := &gen{max: 1 << 15} if err != nil {
o := buffer.Options{ t.Fatal(err)
Pool: buffer.NoPool(64), }
ReadSize: 16,
}
r := buffer.ReaderFrom(g, o) if !bytes.Equal(b, generate(48)) {
b, err := r.Peek(16) t.Fatal("invalid content")
if err != nil { }
t.Fatal(err) })
}
if !bytes.Equal(b, generate(16)) { t.Run("peek on partially filled multiple segments", func(t *testing.T) {
t.Fatal("invalid content") g := &gen{max: 1 << 15}
} o := buffer.Options{Pool: buffer.NoPool(64)}
r := cr(g, o)
b, err := r.Peek(160)
if err != nil {
t.Fatal(err)
}
b, err = r.Peek(48) if !bytes.Equal(b, generate(160)) {
if err != nil { t.Fatal("invalid content")
t.Fatal(err) }
}
if !bytes.Equal(b, generate(48)) { b, err = r.Peek(144)
t.Fatal("invalid content") if err != nil {
} t.Fatal(err)
}) }
t.Run("peek on partially filled multiple segments", func(t *testing.T) { if !bytes.Equal(b, generate(144)) {
g := &gen{max: 1 << 15} t.Fatal("invalid content")
o := buffer.Options{ }
Pool: buffer.NoPool(64), })
ReadSize: 16,
}
r := buffer.ReaderFrom(g, o) t.Run("peek not enough available", func(t *testing.T) {
b, err := r.Peek(160) g := &gen{
if err != nil { max: 12,
t.Fatal(err) fastErr: true,
} }
if !bytes.Equal(b, generate(160)) { o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
t.Fatal("invalid content") r := cr(g, o)
} b, err := r.Peek(18)
if err != nil {
t.Fatal(err)
}
b, err = r.Peek(144) if !bytes.Equal(b, generate(12)) {
if err != nil { t.Fatal("invalid content")
t.Fatal(err) }
} })
if !bytes.Equal(b, generate(144)) { t.Run("peek not enough available multiple segments", func(t *testing.T) {
t.Fatal("invalid content") g := &gen{
} max: 144,
}) fastErr: true,
}
t.Run("peek not enough available", func(t *testing.T) { o := buffer.Options{Pool: buffer.NoPool(64)}
g := &gen{ r := cr(g, o)
max: 12, b, err := r.Peek(214)
fastErr: true, if err != nil {
} t.Fatal(err)
}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} if !bytes.Equal(b, generate(144)) {
r := buffer.ReaderFrom(g, o) t.Fatal("invalid content")
b, err := r.Peek(18) }
if err != nil { })
t.Fatal(err)
}
if !bytes.Equal(b, generate(12)) { t.Run("peek zero", func(t *testing.T) {
t.Fatal("invalid content") g := &gen{max: 1 << 15}
} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
}) r := cr(g, o)
b, err := r.Peek(0)
if err != nil {
t.Fatal(err)
}
t.Run("peek not enough available multiple segments", func(t *testing.T) { if len(b) != 0 {
g := &gen{ t.Fatal("ivnalid content")
max: 144, }
fastErr: true, })
} })
}
o := buffer.Options{
Pool: buffer.NoPool(64),
ReadSize: 16,
}
r := buffer.ReaderFrom(g, o)
b, err := r.Peek(214)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, generate(144)) {
t.Fatal("invalid content")
}
})
t.Run("peek zero", func(t *testing.T) {
g := &gen{max: 1 << 15}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := buffer.ReaderFrom(g, o)
b, err := r.Peek(0)
if err != nil {
t.Fatal(err)
}
if len(b) != 0 {
t.Fatal("ivnalid content")
}
})
} }

View File

@ -1,6 +1,6 @@
package buffer package buffer
type noPool struct{ type noPool struct {
allocSize int allocSize int
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,371 +1,280 @@
package buffer_test package buffer_test
import ( import (
"testing"
"code.squareroundforest.org/arpio/buffer"
"io"
"bytes" "bytes"
"code.squareroundforest.org/arpio/buffer"
"errors" "errors"
"io"
"testing"
) )
func TestRead(t *testing.T) { func TestRead(t *testing.T) {
t.Run("small", func(t *testing.T) { for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} {
g := &gen{max: 3} t.Run(title, func(t *testing.T) {
r := buffer.ReaderFrom(g, buffer.Options{Pool: buffer.NoPool(1 << 12)}) t.Run("small", func(t *testing.T) {
b, err := io.ReadAll(r) g := &gen{max: 3}
if err != nil { r := cr(g, buffer.Options{Pool: buffer.NoPool(1 << 12)})
t.Fatal(err) b, err := io.ReadAll(r)
} if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, generate(3)) { if !bytes.Equal(b, generate(3)) {
t.Fatal("output does not match", len(b)) t.Fatal("output does not match", len(b))
} }
}) })
t.Run("large", func(t *testing.T) { t.Run("large", func(t *testing.T) {
g := &gen{max: 1 << 18} g := &gen{max: 1 << 18}
r := buffer.ReaderFrom(g, buffer.Options{Pool: buffer.NoPool(1 << 12)}) r := cr(g, buffer.Options{Pool: buffer.NoPool(1 << 12)})
b, err := io.ReadAll(r) b, err := io.ReadAll(r)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !bytes.Equal(b, generate(1<<18)) { if !bytes.Equal(b, generate(1<<18)) {
t.Fatal("output does not match", len(b)) t.Fatal("output does not match", len(b))
} }
}) })
t.Run("zero first", func(t *testing.T) { t.Run("zero first", func(t *testing.T) {
g := &gen{max: 1 << 15} g := &gen{max: 1 << 15}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := buffer.ReaderFrom(g, o) r := cr(g, o)
var p []byte var p []byte
n, err := r.Read(p) n, err := r.Read(p)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if n != 0 { if n != 0 {
t.Fatal("invalid length") t.Fatal("invalid length")
} }
p = make([]byte, 256) p = make([]byte, 256)
n, err = r.Read(p) n, err = r.Read(p)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if n != 256 { if n != 256 {
t.Fatal("invalid length") t.Fatal("invalid length")
} }
if !bytes.Equal(p, generate(256)) { if !bytes.Equal(p, generate(256)) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
}) })
t.Run("large with non-divisible fragments", func(t *testing.T) { t.Run("large with non-divisible fragments", func(t *testing.T) {
g := &gen{max: 1 << 15} g := &gen{max: 1 << 15}
o := buffer.Options{ o := buffer.Options{Pool: buffer.NoPool(1 << 12 / 2)}
Pool: buffer.NoPool(1 << 12 / 2), r := cr(g, o)
ReadSize: 1 << 12 / 7, b, err := io.ReadAll(r)
} if err != nil {
t.Fatal(err)
}
r := buffer.ReaderFrom(g, o) if !bytes.Equal(b, generate(1<<15)) {
b, err := io.ReadAll(r) t.Fatal("output does not match", len(b))
if err != nil { }
t.Fatal(err) })
}
if !bytes.Equal(b, generate(1<<15)) { t.Run("partial segment", func(t *testing.T) {
t.Fatal("output does not match", len(b)) g := &gen{max: 1 << 15}
} o := buffer.Options{Pool: buffer.NoPool(128)}
}) r := cr(g, o)
r.Peek(30)
p := make([]byte, 60)
n, err := r.Read(p)
if n != 60 {
t.Fatal("invalid read length", n)
}
t.Run("partial segment", func(t *testing.T) { if err != nil {
g := &gen{max: 1 << 15} t.Fatal(err)
o := buffer.Options{ }
Pool: buffer.NoPool(128),
ReadSize: 8,
}
r := buffer.ReaderFrom(g, o) if !bytes.Equal(p, generate(60)) {
r.Peek(30) t.Fatal("invalid content")
p := make([]byte, 60) }
n, err := r.Read(p) })
if n != 60 {
t.Fatal("invalid read length", n)
}
if err != nil { t.Run("partial segment nth", func(t *testing.T) {
t.Fatal(err) g := &gen{max: 1 << 15}
} o := buffer.Options{Pool: buffer.NoPool(128)}
r := cr(g, o)
r.Peek(2*128 + 30)
p := make([]byte, 2*128+60)
n, err := r.Read(p)
if n != 2*128+60 {
t.Fatal("invalid read length", n)
}
if !bytes.Equal(p, generate(60)) { if err != nil {
t.Fatal("invalid content") t.Fatal(err)
} }
})
t.Run("partial segment nth", func(t *testing.T) { if !bytes.Equal(p, generate(2*128+60)) {
g := &gen{max: 1 << 15} t.Fatal("invalid content")
o := buffer.Options{ }
Pool: buffer.NoPool(128), })
ReadSize: 8,
}
r := buffer.ReaderFrom(g, o) t.Run("read buffer larger than read size", func(t *testing.T) {
r.Peek(2*128 + 30) g := &gen{max: 1 << 15}
p := make([]byte, 2*128+60) o := buffer.Options{Pool: buffer.NoPool(128)}
n, err := r.Read(p) r := cr(g, o)
if n != 2*128+60 { p := make([]byte, 12)
t.Fatal("invalid read length", n) n, err := r.Read(p)
} if n != 12 {
t.Fatal("invalid read size")
}
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !bytes.Equal(p, generate(2*128+60)) { if !bytes.Equal(p, generate(12)) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
}) })
t.Run("read buffer larger than read size", func(t *testing.T) { t.Run("read buffer larger than segment", func(t *testing.T) {
g := &gen{max: 1 << 15} g := &gen{max: 1 << 15}
o := buffer.Options{ o := buffer.Options{Pool: buffer.NoPool(128)}
Pool: buffer.NoPool(128), r := cr(g, o)
ReadSize: 8, p := make([]byte, 192)
} n, err := r.Read(p)
if n != 192 {
t.Fatal("invalid read size")
}
r := buffer.ReaderFrom(g, o) if err != nil {
p := make([]byte, 12) t.Fatal(err)
n, err := r.Read(p) }
if n != 12 {
t.Fatal("invalid read size")
}
if err != nil { if !bytes.Equal(p, generate(192)) {
t.Fatal(err) t.Fatal("invalid content")
} }
})
if !bytes.Equal(p, generate(12)) { t.Run("read buffer larger than available data", func(t *testing.T) {
t.Fatal("invalid content") g := &gen{max: 256}
} o := buffer.Options{Pool: buffer.NoPool(128)}
}) r := cr(g, o)
p := make([]byte, 384)
n, err := r.Read(p)
if n != 256 {
t.Fatal("invalid read size")
}
t.Run("read buffer larger than segment", func(t *testing.T) { if err != nil {
g := &gen{max: 1 << 15} t.Fatal(err)
o := buffer.Options{ }
Pool: buffer.NoPool(128),
ReadSize: 8,
}
r := buffer.ReaderFrom(g, o) if !bytes.Equal(p[:n], generate(256)) {
p := make([]byte, 192) t.Fatal("invalid content")
n, err := r.Read(p) }
if n != 192 {
t.Fatal("invalid read size")
}
if err != nil { n, err = r.Read(p)
t.Fatal(err) if n != 0 || !errors.Is(err, io.EOF) {
} t.Fatal("invalid post read", n, err)
}
})
})
}
if !bytes.Equal(p, generate(192)) { t.Run("reader only", func(t *testing.T) {
t.Fatal("invalid content") cr := buffer.BufferedReader
} t.Run("null read on empty", func(t *testing.T) {
}) g := &gen{
max: 1 << 15,
nullReadAfter: []int{0, 0},
}
t.Run("read buffer larger than available data", func(t *testing.T) { o := buffer.Options{Pool: buffer.NoPool(128)}
g := &gen{max: 256} r := cr(g, o)
o := buffer.Options{ p := make([]byte, 64)
Pool: buffer.NoPool(128), n, err := r.Read(p)
ReadSize: 8, if n != 0 || err != nil {
} t.Fatal("failed to handle null read", n, err)
}
r := buffer.ReaderFrom(g, o) n, err = r.Read(p)
p := make([]byte, 384) if n != 64 || err != nil || !bytes.Equal(p, generate(64)) {
n, err := r.Read(p) t.Fatal("failed to recover after null read", n, err)
if n != 256 { }
t.Fatal("invalid read size") })
}
if err != nil { t.Run("read error without content", func(t *testing.T) {
t.Fatal(err) g := &gen{
} max: 1 << 15,
errAfter: []int{32},
}
if !bytes.Equal(p[:n], generate(256)) { o := buffer.Options{Pool: buffer.NoPool(32)}
t.Fatal("invalid content") r := cr(g, o)
} p := make([]byte, 64)
n, err := r.Read(p)
if n != 32 || err != nil {
t.Fatal("failed to read", n, err)
}
n, err = r.Read(p) n, err = r.Read(p)
if n != 0 || !errors.Is(err, io.EOF) { if n != 0 || !errors.Is(err, errTest) {
t.Fatal("invalid post read", n, err) t.Fatal("failed to process read error", n, err)
} }
}) })
t.Run("null read on empty", func(t *testing.T) { t.Run("read error with content", func(t *testing.T) {
g := &gen{ g := &gen{
max: 1 << 15, max: 1 << 15,
nullReadAfter: []int{0, 0}, errAfter: []int{32},
} fastErr: true,
}
o := buffer.Options{ o := buffer.Options{Pool: buffer.NoPool(32)}
Pool: buffer.NoPool(128), r := cr(g, o)
ReadSize: 8, p := make([]byte, 64)
} n, err := r.Read(p)
if n != 32 || err != nil {
t.Fatal("failed to read", n, err)
}
r := buffer.ReaderFrom(g, o) n, err = r.Read(p)
p := make([]byte, 64) if n != 0 || !errors.Is(err, errTest) {
n, err := r.Read(p) t.Fatal("failed to process read error", n, err)
if n != 0 || err != nil { }
t.Fatal("failed to handle null read", n, err) })
}
n, err = r.Read(p) t.Run("read after error", func(t *testing.T) {
if n != 64 || err != nil || !bytes.Equal(p, generate(64)) { g := &gen{
t.Fatal("failed to recover after null read", n, err) max: 1 << 15,
} errAfter: []int{32},
}) fastErr: true,
}
t.Run("null read on non-empty", func(t *testing.T) { o := buffer.Options{Pool: buffer.NoPool(32)}
g := &gen{ var result []byte
max: 1 << 15, r := cr(g, o)
nullReadAfter: []int{32, 32}, p := make([]byte, 9)
} for i := 0; i < 3; i++ {
if n, err := r.Read(p); n != 9 || err != nil {
t.Fatal(n, err)
}
o := buffer.Options{ result = append(result, p...)
Pool: buffer.NoPool(128), }
ReadSize: 32,
}
r := buffer.ReaderFrom(g, o) if n, err := r.Read(p); n != 5 || err != nil {
p := make([]byte, 32)
n, err := r.Read(p)
if n != 32 || err != nil || !bytes.Equal(p, generate(32)) {
t.Fatal("initial read failed")
}
n, err = r.Read(p)
if n != 0 || err != nil {
t.Fatal("failed to handle null read", n, err)
}
n, err = r.Read(p)
if n != 32 || err != nil || !bytes.Equal(p, generate(64)[32:]) {
t.Fatal("failed to recover after null read", n, err)
}
})
t.Run("partial read on null read", func(t *testing.T) {
g := &gen{
max: 1 << 15,
nullReadAfter: []int{32, 32},
}
o := buffer.Options{
Pool: buffer.NoPool(128),
ReadSize: 32,
}
r := buffer.ReaderFrom(g, o)
p := make([]byte, 24)
n, err := r.Read(p)
if n != 24 || err != nil || !bytes.Equal(p, generate(24)) {
t.Fatal("initial read failed")
}
n, err = r.Read(p)
if n != 8 || err != nil || !bytes.Equal(p[:n], generate(32)[24:]) {
t.Fatal("failed to handle null read", n, err)
}
n, err = r.Read(p)
if n != 24 || err != nil || !bytes.Equal(p, generate(56)[32:]) {
t.Fatal("failed to recover after null read", n, err)
}
})
t.Run("read error without content", func(t *testing.T) {
g := &gen{
max: 1 << 15,
errAfter: []int{32},
}
o := buffer.Options{
Pool: buffer.NoPool(128),
ReadSize: 32,
}
r := buffer.ReaderFrom(g, o)
p := make([]byte, 64)
n, err := r.Read(p)
if n != 64 || err != nil {
t.Fatal("failed to read", n, err)
}
n, err = r.Read(p)
if n != 0 || !errors.Is(err, errTest) {
t.Fatal("failed to process read error", n, err)
}
})
t.Run("read error with content", func(t *testing.T) {
g := &gen{
max: 1 << 15,
errAfter: []int{32},
fastErr: true,
}
o := buffer.Options{
Pool: buffer.NoPool(128),
ReadSize: 32,
}
r := buffer.ReaderFrom(g, o)
p := make([]byte, 64)
n, err := r.Read(p)
if n != 64 || err != nil {
t.Fatal("failed to read", n, err)
}
n, err = r.Read(p)
if n != 0 || !errors.Is(err, errTest) {
t.Fatal("failed to process read error", n, err)
}
})
t.Run("read after error", func(t *testing.T) {
g := &gen{
max: 1 << 15,
errAfter: []int{32},
fastErr: true,
}
o := buffer.Options{
Pool: buffer.NoPool(128),
ReadSize: 32,
}
var result []byte
r := buffer.ReaderFrom(g, o)
p := make([]byte, 9)
for i := 0; i < 3; i++ {
if n, err := r.Read(p); n != 9 || err != nil {
t.Fatal(n, err) t.Fatal(n, err)
} }
result = append(result, p...) result = append(result, p[:5]...)
} if !bytes.Equal(result, generate(32)) {
t.Fatal("invalid content")
if n, err := r.Read(p); n != 5 || err != nil { }
t.Fatal(n, err) })
}
result = append(result, p[:5]...)
if !bytes.Equal(result, generate(32)) {
t.Fatal("invalid content")
}
}) })
} }

File diff suppressed because it is too large Load Diff

View File

@ -16,34 +16,21 @@ type reader struct {
err error err error
} }
func (r *reader) fillSegment(n int) (fn int) { func (r *reader) fillSegment() (fn int) {
for { for {
if r.err != nil { if r.err != nil {
return return
} }
if fn >= n {
return
}
seg := r.segments[len(r.segments)-1] seg := r.segments[len(r.segments)-1]
start := r.offset + r.len - r.lastSegStart start := r.offset + r.len - r.lastSegStart
end := start + n - fn if start == len(seg) {
if end-start < r.options.ReadSize {
end = start + r.options.ReadSize
}
if end > len(seg) {
end = len(seg)
}
if end == start {
return return
} }
rn, err := r.in.Read(seg[start:end]) rn, err := r.in.Read(seg[start:len(seg)])
if rn == 0 && err == nil { if rn == 0 && err == nil {
rn, err = r.in.Read(seg[start:end]) rn, err = r.in.Read(seg[start:len(seg)])
} }
if rn == 0 && err == nil { if rn == 0 && err == nil {
@ -92,7 +79,7 @@ func (r *reader) fill(n int) {
} }
} }
fn := r.fillSegment(n) fn := r.fillSegment()
if fn == 0 { if fn == 0 {
return return
} }
@ -154,12 +141,18 @@ func (r *reader) consume(n int) {
func (r *reader) free() { func (r *reader) free() {
for { for {
if len(r.segments) == 0 { if len(r.segments) == 0 {
return break
} }
r.options.Pool.Put(r.segments[0]) r.options.Pool.Put(r.segments[0])
r.segments = r.segments[1:] r.segments = r.segments[1:]
} }
if c, ok := r.in.(interface{ close() error }); ok {
if err := c.close(); err != nil {
r.err = errors.Join(r.err, err)
}
}
} }
func (r reader) findSegmentUp(i int) (int, int) { func (r reader) findSegmentUp(i int) (int, int) {
@ -297,18 +290,29 @@ func (r *reader) read(p []byte) (int, error) {
return 0, r.err return 0, r.err
} }
// TODO: var n int
// - consider optimizing such that it reads to and copies from the existing segment for len(p) > 0 {
// - pool.put errors are not really reader errors if len(r.segments) == 0 {
// - pool.get errors are not really reader errors, if the reader can still finish its task r.allocate()
// - consider optimizing other places if r.err != nil {
// - first implement the pool test cases break
// - the size parameter for the pool contradicts the pool functionality }
// - defining the segment sizes for the pool conflicts with the buffer options }
// - is the read size even necessary? Doesn't it always make sense to fill up the current segment?
r.fill(len(p)) if r.len == 0 {
n := r.copy(p) r.fillSegment()
r.consume(n) }
ni := r.copy(p)
if ni == 0 {
break
}
r.consume(ni)
p = p[ni:]
n += ni
}
if r.err != nil && r.len == 0 { if r.err != nil && r.len == 0 {
r.free() r.free()
if n == 0 { if n == 0 {
@ -476,7 +480,7 @@ func (r *reader) writeTo(w io.Writer) (int64, error) {
} }
} }
fn := r.fillSegment(len(r.segments[len(r.segments) - 1])) fn := r.fillSegment()
if fn == 0 && r.err == nil { if fn == 0 && r.err == nil {
return n, io.ErrNoProgress return n, io.ErrNoProgress
} }

View File

@ -1,232 +1,235 @@
package buffer_test package buffer_test
import ( import (
"testing"
"code.squareroundforest.org/arpio/buffer" "code.squareroundforest.org/arpio/buffer"
"errors" "errors"
"io" "io"
"testing"
) )
func TestReadUTF8(t *testing.T) { func TestReadUTF8(t *testing.T) {
t.Run("read all after error", func(t *testing.T) { for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} {
g := &gen{ t.Run(title, func(t *testing.T) {
rng: utf8Range, t.Run("read all after error", func(t *testing.T) {
max: 24, g := &gen{
fastErr: true, rng: utf8Range,
} max: 24,
fastErr: true,
}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := buffer.ReaderFrom(g, o) r := cr(g, o)
runes, n0, err := r.ReadUTF8(12) runes, n0, err := r.ReadUTF8(12)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(runes) != 12 { if len(runes) != 12 {
t.Fatal("short read", len(runes)) t.Fatal("short read", len(runes))
} }
if string(runes) != string(generateFrom(utf8Range, n0)) { if string(runes) != string(generateFrom(utf8Range, n0)) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
runes, n1, err := r.ReadUTF8(12) runes, n1, err := r.ReadUTF8(12)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(runes) != 3 { if len(runes) != 3 {
t.Fatal("short read", len(runes)) t.Fatal("short read", len(runes))
} }
if string(runes) != string(generateFrom(utf8Range, n0+n1)[n0:]) { if string(runes) != string(generateFrom(utf8Range, n0+n1)[n0:]) {
t.Fatal("invalid content", string(runes), string(generateFrom(utf8Range, n0+n1)[n0:])) t.Fatal("invalid content", string(runes), string(generateFrom(utf8Range, n0+n1)[n0:]))
} }
runes, n2, err := r.ReadUTF8(12) runes, n2, err := r.ReadUTF8(12)
if !errors.Is(err, io.EOF) { if !errors.Is(err, io.EOF) {
t.Fatal(err) t.Fatal(err)
} }
if n2 != 0 { if n2 != 0 {
t.Fatal("unexpected consumption") t.Fatal("unexpected consumption")
} }
if len(runes) != 0 { if len(runes) != 0 {
t.Fatal("unexpected read", len(runes)) t.Fatal("unexpected read", len(runes))
} }
}) })
t.Run("ascii", func(t *testing.T) { t.Run("ascii", func(t *testing.T) {
g := &gen{max: 1 << 15} g := &gen{max: 1 << 15}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := buffer.ReaderFrom(g, o) r := cr(g, o)
runes, n, err := r.ReadUTF8(12) runes, n, err := r.ReadUTF8(12)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(runes) != 12 { if len(runes) != 12 {
t.Fatal("short read", len(runes)) t.Fatal("short read", len(runes))
} }
if string(runes) != string(generate(n)) { if string(runes) != string(generate(n)) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
}) })
t.Run("long within segment", func(t *testing.T) { t.Run("long within segment", func(t *testing.T) {
g := &gen{ g := &gen{
rng: utf8W2Range, rng: utf8W2Range,
max: 1 << 15, max: 1 << 15,
} }
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := buffer.ReaderFrom(g, o) r := cr(g, o)
runes, n, err := r.ReadUTF8(12) runes, n, err := r.ReadUTF8(12)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(runes) != 12 { if len(runes) != 12 {
t.Fatal("short read", len(runes)) t.Fatal("short read", len(runes))
} }
if string(runes) != string(generateFrom(utf8W2Range, n)) { if string(runes) != string(generateFrom(utf8W2Range, n)) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
}) })
t.Run("long across segments", func(t *testing.T) { t.Run("long across segments", func(t *testing.T) {
g := &gen{ g := &gen{
rng: utf8W2Range, rng: utf8W2Range,
max: 1 << 15, max: 1 << 15,
} }
o := buffer.Options{Pool: buffer.NoPool(9)} o := buffer.Options{Pool: buffer.NoPool(9)}
r := buffer.ReaderFrom(g, o) r := cr(g, o)
runes, n, err := r.ReadUTF8(12) runes, n, err := r.ReadUTF8(12)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(runes) != 12 { if len(runes) != 12 {
t.Fatal("short read", len(runes)) t.Fatal("short read", len(runes))
} }
if string(runes) != string(generateFrom(utf8W2Range, n)) { if string(runes) != string(generateFrom(utf8W2Range, n)) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
}) })
t.Run("null read", func(t *testing.T) { t.Run("broken unicode at the end", func(t *testing.T) {
const numRunes = 6 brokenRange := []byte{0xc3, 0xc3, 0xc3}
nullReadAfter := len(string([]rune(string(utf8W2Range))[:numRunes])) g := &gen{
g := &gen{ rng: utf8Range,
rng: utf8W2Range, max: len(utf8Range) + len(brokenRange),
max: 1 << 15, customContentAfter: []int{len(utf8Range)},
nullReadAfter: []int{nullReadAfter, nullReadAfter}, customContent: map[int][]byte{len(utf8Range): brokenRange},
} }
o := buffer.Options{ o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
Pool: buffer.NoPool(nullReadAfter), r := cr(g, o)
ReadSize: nullReadAfter, runes, n, err := r.ReadUTF8(24)
} if err != nil {
t.Fatal(err)
}
r := buffer.ReaderFrom(g, o) if string(runes) != string(generateFrom(utf8Range, n)) {
runes, _, err := r.ReadUTF8(numRunes - 2) // -2 for min read in readUTF8 t.Fatal("invalid content", string(runes), string(generateFrom(utf8Range, n)))
if err != nil { }
t.Fatal(err)
}
if len(runes) != numRunes-2 { if len([]byte(string(runes))) != len(utf8Range) {
t.Fatal("short read", len(runes)) t.Fatal("invalid number of bytes")
} }
if string(runes) != string(generateFrom(utf8W2Range, nullReadAfter-4)) { for i := 0; i < 3; i++ {
t.Fatal("invalid content") runes, n, err = r.ReadUTF8(24)
} if len(runes) != 0 || n != 1 || err != nil {
t.Fatal("failed to read out broken end")
}
}
runes, _, err = r.ReadUTF8(12) runes, n, err = r.ReadUTF8(24)
if err != nil { if len(runes) != 0 || n != 0 || !errors.Is(err, io.EOF) {
t.Fatal(err) t.Fatal("failed to read EOF", len(runes), n, err)
} }
})
if len(runes) != 2 { t.Run("immediate err", func(t *testing.T) {
t.Fatal("short read 2", len(runes)) g := &gen{rng: utf8Range}
} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := cr(g, o)
runes, n, err := r.ReadUTF8(12)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if string(runes) != string(generateFrom(utf8W2Range, nullReadAfter)[nullReadAfter-4:]) { if n != 0 {
t.Fatal("invalid content 2") t.Fatal("unexpected read", n)
} }
runes, _, err = r.ReadUTF8(12) if len(runes) != 0 {
if err != nil { t.Fatal("unexpected content")
t.Fatal(err) }
} })
})
}
if len(runes) != 12 { t.Run("reader only", func(t *testing.T) {
t.Fatal("short read 3", len(runes)) cr := buffer.BufferedReader
} t.Run("null read", func(t *testing.T) {
const numRunes = 6
if string(runes) != string(generateFrom(utf8W2Range, 3*nullReadAfter)[nullReadAfter:]) { nullReadAfter := len(string([]rune(string(utf8W2Range))[:numRunes]))
t.Fatal("invalid content 3") g := &gen{
} rng: utf8W2Range,
}) max: 1 << 15,
nullReadAfter: []int{nullReadAfter, nullReadAfter},
t.Run("broken unicode at the end", func(t *testing.T) {
brokenRange := []byte{0xc3, 0xc3, 0xc3}
g := &gen{
rng: utf8Range,
max: len(utf8Range) + len(brokenRange),
customContentAfter: []int{len(utf8Range)},
customContent: map[int][]byte{len(utf8Range): brokenRange},
}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := buffer.ReaderFrom(g, o)
runes, n, err := r.ReadUTF8(24)
if err != nil {
t.Fatal(err)
}
if string(runes) != string(generateFrom(utf8Range, n)) {
t.Fatal("invalid content", string(runes), string(generateFrom(utf8Range, n)))
}
if len([]byte(string(runes))) != len(utf8Range) {
t.Fatal("invalid number of bytes")
}
for i := 0; i < 3; i++ {
runes, n, err = r.ReadUTF8(24)
if len(runes) != 0 || n != 1 || err != nil {
t.Fatal("failed to read out broken end")
} }
}
runes, n, err = r.ReadUTF8(24) o := buffer.Options{Pool: buffer.NoPool(nullReadAfter)}
if len(runes) != 0 || n != 0 || !errors.Is(err, io.EOF) { r := cr(g, o)
t.Fatal("failed to read EOF", len(runes), n, err) runes, _, err := r.ReadUTF8(numRunes - 2) // -2 for min read in readUTF8
} if err != nil {
}) t.Fatal(err)
}
t.Run("immediate err", func(t *testing.T) { if len(runes) != numRunes-2 {
g := &gen{rng: utf8Range} t.Fatal("short read", len(runes))
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} }
r := buffer.ReaderFrom(g, o)
runes, n, err := r.ReadUTF8(12)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if n != 0 { if string(runes) != string(generateFrom(utf8W2Range, nullReadAfter-4)) {
t.Fatal("unexpected read", n) t.Fatal("invalid content")
} }
if len(runes) != 0 { runes, _, err = r.ReadUTF8(12)
t.Fatal("unexpected content") if err != nil {
} t.Fatal(err)
}
if len(runes) != 2 {
t.Fatal("short read 2", len(runes))
}
if string(runes) != string(generateFrom(utf8W2Range, nullReadAfter)[nullReadAfter-4:]) {
t.Fatal("invalid content 2")
}
runes, _, err = r.ReadUTF8(12)
if err != nil {
t.Fatal(err)
}
if len(runes) != 12 {
t.Fatal("short read 3", len(runes))
}
if string(runes) != string(generateFrom(utf8W2Range, 3*nullReadAfter)[nullReadAfter:]) {
t.Fatal("invalid content 3")
}
})
}) })
} }

View File

@ -1,285 +1,288 @@
package buffer_test package buffer_test
import ( import (
"testing"
"code.squareroundforest.org/arpio/buffer"
"bytes" "bytes"
"code.squareroundforest.org/arpio/buffer"
"errors" "errors"
"io" "io"
"testing"
) )
func TestWriteTo(t *testing.T) { func TestWriteTo(t *testing.T) {
t.Run("write out from zero", func(t *testing.T) { for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} {
g := &gen{max: 1 << 15} t.Run(title, func(t *testing.T) {
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} t.Run("write out from zero", func(t *testing.T) {
r := buffer.ReaderFrom(g, o) g := &gen{max: 1 << 15}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := cr(g, o)
var b bytes.Buffer var b bytes.Buffer
n, err := r.WriteTo(&b) n, err := r.WriteTo(&b)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if n != 1 << 15 { if n != 1<<15 {
t.Fatal("write count") t.Fatal("write count")
} }
if !bytes.Equal(b.Bytes(), generate(1 << 15)) { if !bytes.Equal(b.Bytes(), generate(1<<15)) {
t.Fatal("content") t.Fatal("content")
} }
}) })
t.Run("write out from started", func(t *testing.T) { t.Run("write out from started", func(t *testing.T) {
g := &gen{max: 1 << 15} g := &gen{max: 1 << 15}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := buffer.ReaderFrom(g, o) r := cr(g, o)
p := make([]byte, 256) p := make([]byte, 256)
n, err := r.Read(p) n, err := r.Read(p)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if n != 256 { if n != 256 {
t.Fatal("invalid read count") t.Fatal("invalid read count")
} }
if !bytes.Equal(p, generate(256)) { if !bytes.Equal(p, generate(256)) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
var b bytes.Buffer var b bytes.Buffer
n64, err := r.WriteTo(&b) n64, err := r.WriteTo(&b)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if n64 != 1 << 15 - 256 { if n64 != 1<<15-256 {
t.Fatal("write count") t.Fatal("write count")
} }
if !bytes.Equal(b.Bytes(), generate(1 << 15)[256:]) { if !bytes.Equal(b.Bytes(), generate(1 << 15)[256:]) {
t.Fatal("content") t.Fatal("content")
} }
}) })
t.Run("after EOF", func(t *testing.T) { t.Run("after EOF", func(t *testing.T) {
g := &gen{max: 256} g := &gen{max: 256}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := buffer.ReaderFrom(g, o) r := cr(g, o)
b, err := r.Peek(512) b, err := r.Peek(512)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !bytes.Equal(b, generate(256)) { if !bytes.Equal(b, generate(256)) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
var buf bytes.Buffer var buf bytes.Buffer
n, err := r.WriteTo(&buf) n, err := r.WriteTo(&buf)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if n != 256 { if n != 256 {
t.Fatal("write count") t.Fatal("write count")
} }
if !bytes.Equal(buf.Bytes(), generate(256)) { if !bytes.Equal(buf.Bytes(), generate(256)) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
}) })
t.Run("after err", func(t *testing.T) { t.Run("after eof empty", func(t *testing.T) {
g := &gen{ g := &gen{max: 256}
max: 1 << 15, o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
errAfter: []int{256}, r := cr(g, o)
fastErr: true, p := make([]byte, 512)
} n, err := r.Read(p)
if err != nil {
t.Fatal(err)
}
o := buffer.Options{ if n != 256 {
Pool: buffer.NoPool(64), t.Fatal("invalid length")
ReadSize: 16, }
}
r := buffer.ReaderFrom(g, o) if !bytes.Equal(p[:n], generate(256)) {
b, err := r.Peek(512) t.Fatal("invalid content 1")
if err != nil { }
t.Fatal(err)
}
if !bytes.Equal(b, generate(256)) { var buf bytes.Buffer
t.Fatal("invalid content", 1) n64, err := r.WriteTo(&buf)
} if err != nil {
t.Fatal(err)
}
var buf bytes.Buffer if n64 != 0 {
n, err := r.WriteTo(&buf) t.Fatal("write count")
if !errors.Is(err, errTest) { }
t.Fatal("failed to test with the right error", err)
}
if n != 256 { if buf.Len() != 0 {
t.Fatal("write count") t.Fatal("invalid content 2")
} }
})
if !bytes.Equal(buf.Bytes(), generate(256)) { t.Run("write error", func(t *testing.T) {
t.Fatal("invalid content", 2) g := &gen{max: 1 << 15}
} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
}) r := cr(g, o)
w := &writer{errAfter: []int{256}}
n, err := r.WriteTo(w)
if !errors.Is(err, errTest) {
t.Fatal("failed to fail with the right error", err)
}
t.Run("after eof empty", func(t *testing.T) { if n < 256 {
g := &gen{max: 256} t.Fatal("not enough written")
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} }
r := buffer.ReaderFrom(g, o)
p := make([]byte, 512)
n, err := r.Read(p)
if err != nil {
t.Fatal(err)
}
if n != 256 { if !bytes.Equal(w.written, generate(1 << 15)[:n]) {
t.Fatal("invalid length") t.Fatal("invalid content")
} }
})
if !bytes.Equal(p[:n], generate(256)) { t.Run("short write", func(t *testing.T) {
t.Fatal("invalid content 1") g := &gen{max: 1 << 15}
} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := cr(g, o)
w := &writer{shortAfter: []int{256}}
n, err := r.WriteTo(w)
if !errors.Is(err, io.ErrShortWrite) {
t.Fatal("failed to fail with the right error", err)
}
var buf bytes.Buffer if n < 256 {
n64, err := r.WriteTo(&buf) t.Fatal("not enough written")
if err != nil { }
t.Fatal(err)
}
if n64 != 0 { if !bytes.Equal(w.written, generate(1 << 15)[:n]) {
t.Fatal("write count") t.Fatal("invalid content")
} }
})
})
}
if buf.Len() != 0 { t.Run("reader only", func(t *testing.T) {
t.Fatal("invalid content 2") cr := buffer.BufferedReader
} t.Run("after err", func(t *testing.T) {
}) g := &gen{
max: 1 << 15,
errAfter: []int{256},
fastErr: true,
}
t.Run("after error empty", func(t *testing.T) { o := buffer.Options{Pool: buffer.NoPool(64)}
g := &gen{ r := cr(g, o)
max: 1 << 15, b, err := r.Peek(512)
errAfter: []int{256}, if err != nil {
} t.Fatal(err)
}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} if !bytes.Equal(b, generate(256)) {
r := buffer.ReaderFrom(g, o) t.Fatal("invalid content", 1)
p := make([]byte, 1 << 15) }
n, err := r.Read(p)
if err != nil {
t.Fatal(err)
}
if n < 256 { var buf bytes.Buffer
t.Fatal("invalid length") n, err := r.WriteTo(&buf)
} if !errors.Is(err, errTest) {
t.Fatal("failed to test with the right error", err)
}
if !bytes.Equal(p[:n], generate(1 << 15)[:n]) { if n != 256 {
t.Fatal("invalid content 1") t.Fatal("write count")
} }
var buf bytes.Buffer if !bytes.Equal(buf.Bytes(), generate(256)) {
n64, err := r.WriteTo(&buf) t.Fatal("invalid content", 2)
if !errors.Is(err, errTest) { }
t.Fatal(err) })
}
if n64 != 0 { t.Run("after error empty", func(t *testing.T) {
t.Fatal("write count") g := &gen{
} max: 1 << 15,
errAfter: []int{256},
}
if buf.Len() != 0 { o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
t.Fatal("invalid content 2") r := cr(g, o)
} p := make([]byte, 1<<15)
}) n, err := r.Read(p)
if err != nil {
t.Fatal(err)
}
t.Run("null read on fill", func(t *testing.T) { if n < 256 {
g := &gen{ t.Fatal("invalid length")
max: 1 << 15, }
nullReadAfter: []int{256, 256},
}
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} if !bytes.Equal(p[:n], generate(1 << 15)[:n]) {
r := buffer.ReaderFrom(g, o) t.Fatal("invalid content 1")
}
var b bytes.Buffer var buf bytes.Buffer
n, err := r.WriteTo(&b) n64, err := r.WriteTo(&buf)
if !errors.Is(err, io.ErrNoProgress) { if !errors.Is(err, errTest) {
t.Fatal("failed to fail with the right error") t.Fatal(err)
} }
if n < 256 { if n64 != 0 {
t.Fatal("not enough written") t.Fatal("write count")
} }
if !bytes.Equal(b.Bytes(), generate(1 << 15)[:n]) { if buf.Len() != 0 {
t.Fatal("invalid content") t.Fatal("invalid content 2")
} }
}) })
t.Run("err during fill", func(t *testing.T) { t.Run("null read on fill", func(t *testing.T) {
g := &gen{ g := &gen{
max: 1 << 15, max: 1 << 15,
errAfter: []int{256}, nullReadAfter: []int{256, 256},
} }
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
r := buffer.ReaderFrom(g, o) r := cr(g, o)
var b bytes.Buffer var b bytes.Buffer
n, err := r.WriteTo(&b) n, err := r.WriteTo(&b)
if !errors.Is(err, errTest) { if !errors.Is(err, io.ErrNoProgress) {
t.Fatal("failed to fail with the right error") t.Fatal("failed to fail with the right error")
} }
if n < 256 { if n < 256 {
t.Fatal("not enough written") t.Fatal("not enough written")
} }
if !bytes.Equal(b.Bytes(), generate(1 << 15)[:n]) { if !bytes.Equal(b.Bytes(), generate(1 << 15)[:n]) {
t.Fatal("invalid content") t.Fatal("invalid content")
} }
}) })
t.Run("write error", func(t *testing.T) { t.Run("err during fill", func(t *testing.T) {
g := &gen{max: 1 << 15} g := &gen{
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} max: 1 << 15,
r := buffer.ReaderFrom(g, o) errAfter: []int{256},
w := &writer{errAfter: []int{256}} }
n, err := r.WriteTo(w)
if !errors.Is(err, errTest) {
t.Fatal("failed to fail with the right error", err)
}
if n < 256 { o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
t.Fatal("not enough written") r := cr(g, o)
}
if !bytes.Equal(w.written, generate(1 << 15)[:n]) { var b bytes.Buffer
t.Fatal("invalid content") n, err := r.WriteTo(&b)
} if !errors.Is(err, errTest) {
}) t.Fatal("failed to fail with the right error")
}
t.Run("short write", func(t *testing.T) { if n < 256 {
g := &gen{max: 1 << 15} t.Fatal("not enough written")
o := buffer.Options{Pool: buffer.NoPool(1 << 12)} }
r := buffer.ReaderFrom(g, o)
w := &writer{shortAfter: []int{256}}
n, err := r.WriteTo(w)
if !errors.Is(err, io.ErrShortWrite) {
t.Fatal("failed to fail with the right error", err)
}
if n < 256 { if !bytes.Equal(b.Bytes(), generate(1 << 15)[:n]) {
t.Fatal("not enough written") t.Fatal("invalid content")
} }
})
if !bytes.Equal(w.written, generate(1 << 15)[:n]) {
t.Fatal("invalid content")
}
}) })
} }