content reader and testing
This commit is contained in:
parent
3c238b5248
commit
83801867f4
@ -1,16 +1,18 @@
|
|||||||
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) {
|
||||||
|
for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} {
|
||||||
|
t.Run(title, func(t *testing.T) {
|
||||||
t.Run("none buffered", func(t *testing.T) {
|
t.Run("none buffered", 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)
|
||||||
b := r.Buffered()
|
b := r.Buffered()
|
||||||
if len(b) != 0 {
|
if len(b) != 0 {
|
||||||
t.Fatal("invalid content")
|
t.Fatal("invalid content")
|
||||||
@ -24,7 +26,7 @@ func TestBuffered(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@ -42,12 +44,8 @@ func TestBuffered(t *testing.T) {
|
|||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, err := r.Peek(48)
|
b, err := r.Peek(48)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -58,19 +56,15 @@ func TestBuffered(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b = r.Buffered()
|
b = r.Buffered()
|
||||||
if !bytes.Equal(b, generate(48)) {
|
if !bytes.Equal(b, generate(64)) {
|
||||||
t.Fatal("invalid content 2", len(b))
|
t.Fatal("invalid content 2", len(b))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("buffered across segments", func(t *testing.T) {
|
t.Run("buffered across segments", 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, err := r.Peek(144)
|
b, err := r.Peek(144)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -88,12 +82,8 @@ func TestBuffered(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("buffered mid segment", func(t *testing.T) {
|
t.Run("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(128)}
|
||||||
Pool: buffer.NoPool(128),
|
r := cr(g, o)
|
||||||
ReadSize: 64,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b := make([]byte, 32)
|
b := make([]byte, 32)
|
||||||
n, err := r.Read(b)
|
n, err := r.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -105,51 +95,38 @@ func TestBuffered(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(b, generate(32)) {
|
if !bytes.Equal(b, generate(32)) {
|
||||||
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(128)[32:]) {
|
||||||
t.Fatal("invalid content")
|
t.Fatal("invalid content 2")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("buffered mid segment across segments", func(t *testing.T) {
|
t.Run("buffered mid segment across segments", 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: 64,
|
b, err := r.Peek(288)
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b := make([]byte, 288)
|
|
||||||
n, err := r.Read(b)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if n != 288 {
|
|
||||||
t.Fatal("invalid read length", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(b, generate(288)) {
|
if !bytes.Equal(b, generate(288)) {
|
||||||
t.Fatal("invalid content 1")
|
t.Fatal("invalid content 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
b = r.Buffered()
|
b = r.Buffered()
|
||||||
if !bytes.Equal(b, generate(384)[288:]) {
|
if !bytes.Equal(b, generate(384)) {
|
||||||
t.Fatal("invalid content 2", len(b))
|
t.Fatal("invalid content 2", len(b))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("zero buffered mid segment", 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b := make([]byte, 64)
|
b := make([]byte, 64)
|
||||||
n, err := r.Read(b)
|
n, err := r.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -169,4 +146,6 @@ func TestBuffered(t *testing.T) {
|
|||||||
t.Fatal("invalid content 2", len(b))
|
t.Fatal("invalid content 2", len(b))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
120
content.go
Normal file
120
content.go
Normal 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
341
content_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
14
io_test.go
14
io_test.go
@ -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
|
||||||
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
73
lib.go
73
lib.go
@ -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
|
||||||
|
// 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{}
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
60
peek_test.go
60
peek_test.go
@ -1,22 +1,20 @@
|
|||||||
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) {
|
||||||
|
for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} {
|
||||||
|
t.Run(title, func(t *testing.T) {
|
||||||
t.Run("peek across segments", func(t *testing.T) {
|
t.Run("peek across segments", 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
p, err := r.Peek(2*128 + 30)
|
p, err := r.Peek(2*128 + 30)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -34,7 +32,7 @@ func TestPeek(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@ -75,7 +73,7 @@ func TestPeek(t *testing.T) {
|
|||||||
t.Run("err immediately on first try to fill", func(t *testing.T) {
|
t.Run("err immediately on first try to fill", func(t *testing.T) {
|
||||||
g := &gen{}
|
g := &gen{}
|
||||||
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 !errors.Is(err, io.EOF) {
|
if !errors.Is(err, io.EOF) {
|
||||||
t.Fatal("failed EOF")
|
t.Fatal("failed EOF")
|
||||||
@ -89,7 +87,7 @@ func TestPeek(t *testing.T) {
|
|||||||
t.Run("peek on empty", func(t *testing.T) {
|
t.Run("peek on empty", 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)
|
||||||
b, err := r.Peek(18)
|
b, err := r.Peek(18)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -102,12 +100,8 @@ func TestPeek(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("peek multiple segments", func(t *testing.T) {
|
t.Run("peek multiple segments", 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, err := r.Peek(160)
|
b, err := r.Peek(160)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -120,12 +114,8 @@ func TestPeek(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("peek on partially filled", func(t *testing.T) {
|
t.Run("peek on partially filled", 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, err := r.Peek(16)
|
b, err := r.Peek(16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -147,12 +137,8 @@ func TestPeek(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("peek on partially filled multiple segments", func(t *testing.T) {
|
t.Run("peek on partially filled multiple segments", 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, err := r.Peek(160)
|
b, err := r.Peek(160)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -179,7 +165,7 @@ func TestPeek(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@ -196,12 +182,8 @@ func TestPeek(t *testing.T) {
|
|||||||
fastErr: true,
|
fastErr: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(64)}
|
||||||
Pool: buffer.NoPool(64),
|
r := cr(g, o)
|
||||||
ReadSize: 16,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, err := r.Peek(214)
|
b, err := r.Peek(214)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -215,7 +197,7 @@ func TestPeek(t *testing.T) {
|
|||||||
t.Run("peek zero", func(t *testing.T) {
|
t.Run("peek zero", 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)
|
||||||
b, err := r.Peek(0)
|
b, err := r.Peek(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -225,4 +207,6 @@ func TestPeek(t *testing.T) {
|
|||||||
t.Fatal("ivnalid content")
|
t.Fatal("ivnalid content")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
pool.go
2
pool.go
@ -1,6 +1,6 @@
|
|||||||
package buffer
|
package buffer
|
||||||
|
|
||||||
type noPool struct{
|
type noPool struct {
|
||||||
allocSize int
|
allocSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
490
pool_test.go
490
pool_test.go
@ -1,12 +1,12 @@
|
|||||||
package buffer_test
|
package buffer_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
|
||||||
"math/rand"
|
|
||||||
"code.squareroundforest.org/arpio/buffer"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"code.squareroundforest.org/arpio/buffer"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pool struct {
|
type pool struct {
|
||||||
@ -14,9 +14,8 @@ type pool struct {
|
|||||||
alloc, free int
|
alloc, free int
|
||||||
errAfter []int
|
errAfter []int
|
||||||
zeroAfter []int
|
zeroAfter []int
|
||||||
shortAfter []int
|
varyingSize []int
|
||||||
longAfter []int
|
rand *rand.Rand
|
||||||
randomSize []int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p pool) allocCondition(c *[]int) bool {
|
func (p pool) allocCondition(c *[]int) bool {
|
||||||
@ -32,6 +31,14 @@ func (p pool) allocCondition(c *[]int) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *pool) ensureRand() {
|
||||||
|
if p.rand != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.rand = rand.New(rand.NewSource(9))
|
||||||
|
}
|
||||||
|
|
||||||
func (p *pool) Get() ([]byte, error) {
|
func (p *pool) Get() ([]byte, error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
p.alloc++
|
p.alloc++
|
||||||
@ -46,20 +53,14 @@ func (p *pool) Get() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
n := p.allocSize
|
n := p.allocSize
|
||||||
if p.allocCondition(&p.shortAfter) {
|
if len(p.varyingSize) > 1 {
|
||||||
n /= 2
|
p.ensureRand()
|
||||||
|
n = p.varyingSize[0] + p.rand.Intn(p.varyingSize[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.allocCondition(&p.longAfter) {
|
if len(p.varyingSize) == 1 {
|
||||||
n *= 2
|
p.ensureRand()
|
||||||
}
|
n = 1 + p.rand.Intn(p.varyingSize[0])
|
||||||
|
|
||||||
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
|
return make([]byte, n), nil
|
||||||
@ -70,12 +71,13 @@ func (p *pool) Put([]byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPoolUsage(t *testing.T) {
|
func TestPoolUsage(t *testing.T) {
|
||||||
|
for _, cr := range []createReader{buffer.BufferedReader, testContent} {
|
||||||
t.Run("allocate", func(t *testing.T) {
|
t.Run("allocate", func(t *testing.T) {
|
||||||
t.Run("read", func(t *testing.T) {
|
t.Run("read", func(t *testing.T) {
|
||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
p := &pool{allocSize: 1 << 12}
|
p := &pool{allocSize: 1 << 12}
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
b := make([]byte, 256)
|
b := make([]byte, 256)
|
||||||
n, err := r.Read(b)
|
n, err := r.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -99,8 +101,8 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
p := &pool{allocSize: 1 << 9}
|
p := &pool{allocSize: 1 << 9}
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 1 << 12)
|
b, ok, err := r.ReadBytes([]byte("123"), 1<<12)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -122,17 +124,17 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
p := &pool{allocSize: 1 << 12}
|
p := &pool{allocSize: 1 << 12}
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
runes, n, err := r.ReadUTF8(1 << 12)
|
runes, n, err := r.ReadUTF8(1 << 12)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if n != 1 << 12 {
|
if n != 1<<12 {
|
||||||
t.Fatal("unexpected delimiter")
|
t.Fatal("unexpected delimiter")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(runes) != 1 << 12 {
|
if len(runes) != 1<<12 {
|
||||||
t.Fatal("invalid content")
|
t.Fatal("invalid content")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,13 +147,13 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
p := &pool{allocSize: 1 << 12}
|
p := &pool{allocSize: 1 << 12}
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
b, err := r.Peek(3 * 1 << 12)
|
b, err := r.Peek(3 * 1 << 12)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(b) != 3 * 1 << 12 {
|
if len(b) != 3*1<<12 {
|
||||||
t.Fatal("invalid content")
|
t.Fatal("invalid content")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +166,7 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
p := &pool{allocSize: 1 << 12}
|
p := &pool{allocSize: 1 << 12}
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
b := r.Buffered()
|
b := r.Buffered()
|
||||||
if len(b) != 0 {
|
if len(b) != 0 {
|
||||||
t.Fatal("invalid content")
|
t.Fatal("invalid content")
|
||||||
@ -179,7 +181,7 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
p := &pool{allocSize: 1 << 12}
|
p := &pool{allocSize: 1 << 12}
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
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)
|
||||||
@ -187,11 +189,11 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if n != 1 << 15 {
|
if n != 1<<15 {
|
||||||
t.Fatal("invalid write length")
|
t.Fatal("invalid write length")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(b.Bytes(), generate(1 << 15)) {
|
if !bytes.Equal(b.Bytes(), generate(1<<15)) {
|
||||||
t.Fatal("invalid content")
|
t.Fatal("invalid content")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,8 +208,8 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
p := &pool{allocSize: 1 << 12}
|
p := &pool{allocSize: 1 << 12}
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
b := make([]byte, 1 << 9)
|
b := make([]byte, 1<<9)
|
||||||
for {
|
for {
|
||||||
_, err := r.Read(b)
|
_, err := r.Read(b)
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
@ -232,13 +234,13 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
p := &pool{allocSize: 1 << 12}
|
p := &pool{allocSize: 1 << 12}
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
_, _, err := r.ReadBytes([]byte("123"), 1 << 15 + 3)
|
_, _, err := r.ReadBytes([]byte("123"), 1<<15+3)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = r.ReadBytes([]byte("123"), 1 << 15 + 3)
|
_, _, err = r.ReadBytes([]byte("123"), 1<<15+3)
|
||||||
if !errors.Is(err, io.EOF) {
|
if !errors.Is(err, io.EOF) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -256,7 +258,7 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
p := &pool{allocSize: 1 << 12}
|
p := &pool{allocSize: 1 << 12}
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
for {
|
for {
|
||||||
runes, n, err := r.ReadUTF8(1 << 12)
|
runes, n, err := r.ReadUTF8(1 << 12)
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
@ -267,11 +269,11 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if n != 1 << 12 {
|
if n != 1<<12 {
|
||||||
t.Fatal("unexpected delimiter")
|
t.Fatal("unexpected delimiter")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(runes) != 1 << 12 {
|
if len(runes) != 1<<12 {
|
||||||
t.Fatal("invalid content")
|
t.Fatal("invalid content")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -285,13 +287,13 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
p := &pool{allocSize: 1 << 12}
|
p := &pool{allocSize: 1 << 12}
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
b, err := r.Peek(3 * 1 << 12)
|
b, err := r.Peek(3 * 1 << 12)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(b) != 3 * 1 << 12 {
|
if len(b) != 3*1<<12 {
|
||||||
t.Fatal("invalid content")
|
t.Fatal("invalid content")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +305,7 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
t.Fatal("invalid allocation count")
|
t.Fatal("invalid allocation count")
|
||||||
}
|
}
|
||||||
|
|
||||||
b = make([]byte, 1 << 9)
|
b = make([]byte, 1<<9)
|
||||||
for {
|
for {
|
||||||
_, err := r.Read(b)
|
_, err := r.Read(b)
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
@ -328,7 +330,7 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
p := &pool{allocSize: 1 << 12}
|
p := &pool{allocSize: 1 << 12}
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
b := r.Buffered()
|
b := r.Buffered()
|
||||||
if len(b) != 0 {
|
if len(b) != 0 {
|
||||||
t.Fatal("invalid content")
|
t.Fatal("invalid content")
|
||||||
@ -343,7 +345,7 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
p := &pool{allocSize: 1 << 12}
|
p := &pool{allocSize: 1 << 12}
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
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)
|
||||||
@ -351,11 +353,11 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if n != 1 << 15 {
|
if n != 1<<15 {
|
||||||
t.Fatal("invalid write length")
|
t.Fatal("invalid write length")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(b.Bytes(), generate(1 << 15)) {
|
if !bytes.Equal(b.Bytes(), generate(1<<15)) {
|
||||||
t.Fatal("invalid content")
|
t.Fatal("invalid content")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -378,7 +380,7 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
b := make([]byte, 256)
|
b := make([]byte, 256)
|
||||||
_, err := r.Read(b)
|
_, err := r.Read(b)
|
||||||
if !errors.Is(err, buffer.ErrZeroAllocation) {
|
if !errors.Is(err, buffer.ErrZeroAllocation) {
|
||||||
@ -394,13 +396,13 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
_, _, err := r.ReadBytes([]byte("123"), 1 << 12)
|
_, _, err := r.ReadBytes([]byte("123"), 1<<12)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = r.ReadBytes([]byte("123"), 1 << 12)
|
_, _, err = r.ReadBytes([]byte("123"), 1<<12)
|
||||||
if !errors.Is(err, buffer.ErrZeroAllocation) {
|
if !errors.Is(err, buffer.ErrZeroAllocation) {
|
||||||
t.Fatal("failed to fail", err)
|
t.Fatal("failed to fail", err)
|
||||||
}
|
}
|
||||||
@ -414,7 +416,7 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
_, _, err := r.ReadUTF8(1 << 12)
|
_, _, err := r.ReadUTF8(1 << 12)
|
||||||
if !errors.Is(err, buffer.ErrZeroAllocation) {
|
if !errors.Is(err, buffer.ErrZeroAllocation) {
|
||||||
t.Fatal("failed to fail", err)
|
t.Fatal("failed to fail", err)
|
||||||
@ -429,7 +431,7 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
_, err := r.Peek(3 * 1 << 12)
|
_, err := r.Peek(3 * 1 << 12)
|
||||||
if !errors.Is(err, buffer.ErrZeroAllocation) {
|
if !errors.Is(err, buffer.ErrZeroAllocation) {
|
||||||
t.Fatal("failed to fail", err)
|
t.Fatal("failed to fail", err)
|
||||||
@ -444,7 +446,7 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{Pool: p}
|
o := buffer.Options{Pool: p}
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
_, err := r.WriteTo(&b)
|
_, err := r.WriteTo(&b)
|
||||||
@ -454,7 +456,383 @@ func TestPoolUsage(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// varying segment sizes: read bytes, peek
|
t.Run("varying segment sizes", func(t *testing.T) {
|
||||||
// one byte segments: read, read bytes, read utf8, peek, write to
|
t.Run("read bytes", func(t *testing.T) {
|
||||||
// pool error on allocate: read, read bytes, read utf8, peek, write to
|
t.Run("find", func(t *testing.T) {
|
||||||
|
g := &gen{
|
||||||
|
max: 1 << 15,
|
||||||
|
customContentAfter: []int{1 << 11},
|
||||||
|
customContent: map[int][]byte{1 << 11: []byte("123")},
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &pool{varyingSize: []int{8, 256}}
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, ok, err := r.ReadBytes([]byte("123"), 1<<15)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("failed to find delimiter")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(b, append(generate(1<<11), []byte("123")...)) {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("find not", func(t *testing.T) {
|
||||||
|
g := &gen{max: 1 << 15}
|
||||||
|
p := &pool{varyingSize: []int{8, 256}}
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, ok, err := r.ReadBytes([]byte("123"), 1<<15)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
t.Fatal("invalid delimiter")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(b, generate(1<<15)) {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("peek", func(t *testing.T) {
|
||||||
|
g := &gen{max: 1 << 15}
|
||||||
|
p := &pool{varyingSize: []int{8, 256}}
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, err := r.Peek(1 << 11)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(b, generate(1<<11)) {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("one byte segments", func(t *testing.T) {
|
||||||
|
t.Run("read", func(t *testing.T) {
|
||||||
|
g := &gen{max: 1 << 15}
|
||||||
|
p := &pool{allocSize: 1}
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(b, generate(1<<15)) {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("read bytes", func(t *testing.T) {
|
||||||
|
t.Run("find", func(t *testing.T) {
|
||||||
|
g := &gen{
|
||||||
|
max: 1 << 15,
|
||||||
|
customContentAfter: []int{1 << 11},
|
||||||
|
customContent: map[int][]byte{1 << 11: []byte("123")},
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &pool{allocSize: 1}
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, ok, err := r.ReadBytes([]byte("123"), 1<<15)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("failed to find delimiter")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(b, append(generate(1<<11), []byte("123")...)) {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("find not", func(t *testing.T) {
|
||||||
|
g := &gen{max: 1 << 15}
|
||||||
|
p := &pool{allocSize: 1}
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, ok, err := r.ReadBytes([]byte("123"), 1<<15)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
t.Fatal("invalid delimiter found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) != 0 {
|
||||||
|
t.Fatal("invalid content", len(b))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("read utf8", func(t *testing.T) {
|
||||||
|
g := &gen{
|
||||||
|
max: 1 << 15,
|
||||||
|
rng: utf8W2Range,
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &pool{allocSize: 1}
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
runes, n, err := r.ReadUTF8(1 << 14)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != 1<<15 {
|
||||||
|
t.Fatal("invalid read length")
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(runes) != string(generateFrom(utf8W2Range, 1<<15)) {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("peek", func(t *testing.T) {
|
||||||
|
g := &gen{max: 1 << 15}
|
||||||
|
p := &pool{allocSize: 1}
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, err := r.Peek(1 << 14)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(b, generate(1<<14)) {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("write to", func(t *testing.T) {
|
||||||
|
g := &gen{max: 1 << 15}
|
||||||
|
p := &pool{allocSize: 1}
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(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")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pool error on allocate", func(t *testing.T) {
|
||||||
|
t.Run("read", func(t *testing.T) {
|
||||||
|
g := &gen{max: 1 << 15}
|
||||||
|
p := &pool{
|
||||||
|
allocSize: 1 << 11,
|
||||||
|
errAfter: []int{0},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, err := io.ReadAll(r)
|
||||||
|
if !errors.Is(err, errTest) {
|
||||||
|
t.Fatal("failed to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) != 0 {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("read bytes", func(t *testing.T) {
|
||||||
|
t.Run("immediate", func(t *testing.T) {
|
||||||
|
g := &gen{max: 1 << 15}
|
||||||
|
p := &pool{
|
||||||
|
allocSize: 1 << 11,
|
||||||
|
errAfter: []int{0},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, ok, err := r.ReadBytes([]byte("123"), 1<<12)
|
||||||
|
if !errors.Is(err, errTest) {
|
||||||
|
t.Fatal("failed to fail with the right error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
t.Fatal("invalid delimiter found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) != 0 {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("on grow", func(t *testing.T) {
|
||||||
|
g := &gen{max: 1 << 15}
|
||||||
|
p := &pool{
|
||||||
|
allocSize: 1 << 11,
|
||||||
|
errAfter: []int{1},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, ok, err := r.ReadBytes([]byte("123"), 1<<15)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
t.Fatal("invalid delimiter found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(b, generate(1<<11)) {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
|
||||||
|
b, ok, err = r.ReadBytes([]byte("123"), 1<<15)
|
||||||
|
if !errors.Is(err, errTest) {
|
||||||
|
t.Fatal("failed to fail with the right error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
t.Fatal("invalid delimiter found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) != 0 {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("read utf8", func(t *testing.T) {
|
||||||
|
g := &gen{
|
||||||
|
max: 1 << 15,
|
||||||
|
rng: utf8W2Range,
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &pool{
|
||||||
|
allocSize: 1 << 11,
|
||||||
|
errAfter: []int{0},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
runes, n, err := r.ReadUTF8(1 << 14)
|
||||||
|
if !errors.Is(err, errTest) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != 0 {
|
||||||
|
t.Fatal("invalid read length")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(runes) != 0 {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("peek", func(t *testing.T) {
|
||||||
|
t.Run("immediate", func(t *testing.T) {
|
||||||
|
g := &gen{max: 1 << 15}
|
||||||
|
p := &pool{
|
||||||
|
allocSize: 1 << 11,
|
||||||
|
errAfter: []int{0},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, err := r.Peek(1 << 12)
|
||||||
|
if !errors.Is(err, errTest) {
|
||||||
|
t.Fatal("failed to fail with the right error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) != 0 {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("on grow", func(t *testing.T) {
|
||||||
|
g := &gen{max: 1 << 15}
|
||||||
|
p := &pool{
|
||||||
|
allocSize: 1 << 11,
|
||||||
|
errAfter: []int{1},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, err := r.Peek(1 << 15)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(b, generate(1<<11)) {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
|
||||||
|
b, ok, err := r.ReadBytes([]byte("123"), 1<<11)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
t.Fatal("invalid delimiter found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(b, generate(1<<11)) {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = r.Peek(1 << 15)
|
||||||
|
if !errors.Is(err, errTest) {
|
||||||
|
t.Fatal("failed to fail with the right error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) != 0 {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("write to", func(t *testing.T) {
|
||||||
|
g := &gen{max: 1 << 15}
|
||||||
|
p := &pool{
|
||||||
|
allocSize: 1 << 11,
|
||||||
|
errAfter: []int{0},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := buffer.Options{Pool: p}
|
||||||
|
r := cr(g, o)
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
n, err := r.WriteTo(&b)
|
||||||
|
if !errors.Is(err, errTest) {
|
||||||
|
t.Fatal("failed to fail with the right error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != 0 {
|
||||||
|
t.Fatal("invalid write length")
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Len() != 0 {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
161
read_test.go
161
read_test.go
@ -1,17 +1,19 @@
|
|||||||
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) {
|
||||||
|
for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} {
|
||||||
|
t.Run(title, func(t *testing.T) {
|
||||||
t.Run("small", func(t *testing.T) {
|
t.Run("small", func(t *testing.T) {
|
||||||
g := &gen{max: 3}
|
g := &gen{max: 3}
|
||||||
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)
|
||||||
@ -24,7 +26,7 @@ func TestRead(t *testing.T) {
|
|||||||
|
|
||||||
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)
|
||||||
@ -38,7 +40,7 @@ func TestRead(t *testing.T) {
|
|||||||
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)
|
||||||
@ -67,12 +69,8 @@ func TestRead(t *testing.T) {
|
|||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, err := io.ReadAll(r)
|
b, err := io.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -85,12 +83,8 @@ func TestRead(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("partial segment", func(t *testing.T) {
|
t.Run("partial 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
r.Peek(30)
|
r.Peek(30)
|
||||||
p := make([]byte, 60)
|
p := make([]byte, 60)
|
||||||
n, err := r.Read(p)
|
n, err := r.Read(p)
|
||||||
@ -109,12 +103,8 @@ func TestRead(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("partial segment nth", func(t *testing.T) {
|
t.Run("partial segment nth", 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
r.Peek(2*128 + 30)
|
r.Peek(2*128 + 30)
|
||||||
p := make([]byte, 2*128+60)
|
p := make([]byte, 2*128+60)
|
||||||
n, err := r.Read(p)
|
n, err := r.Read(p)
|
||||||
@ -133,12 +123,8 @@ func TestRead(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("read buffer larger than read size", func(t *testing.T) {
|
t.Run("read buffer larger than read size", 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
p := make([]byte, 12)
|
p := make([]byte, 12)
|
||||||
n, err := r.Read(p)
|
n, err := r.Read(p)
|
||||||
if n != 12 {
|
if n != 12 {
|
||||||
@ -156,12 +142,8 @@ func TestRead(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("read buffer larger than segment", 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
p := make([]byte, 192)
|
p := make([]byte, 192)
|
||||||
n, err := r.Read(p)
|
n, err := r.Read(p)
|
||||||
if n != 192 {
|
if n != 192 {
|
||||||
@ -179,12 +161,8 @@ func TestRead(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("read buffer larger than available data", func(t *testing.T) {
|
t.Run("read buffer larger than available data", func(t *testing.T) {
|
||||||
g := &gen{max: 256}
|
g := &gen{max: 256}
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(128)}
|
||||||
Pool: buffer.NoPool(128),
|
r := cr(g, o)
|
||||||
ReadSize: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
p := make([]byte, 384)
|
p := make([]byte, 384)
|
||||||
n, err := r.Read(p)
|
n, err := r.Read(p)
|
||||||
if n != 256 {
|
if n != 256 {
|
||||||
@ -204,19 +182,19 @@ func TestRead(t *testing.T) {
|
|||||||
t.Fatal("invalid post read", n, err)
|
t.Fatal("invalid post read", n, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("reader only", func(t *testing.T) {
|
||||||
|
cr := buffer.BufferedReader
|
||||||
t.Run("null read on empty", func(t *testing.T) {
|
t.Run("null read on empty", func(t *testing.T) {
|
||||||
g := &gen{
|
g := &gen{
|
||||||
max: 1 << 15,
|
max: 1 << 15,
|
||||||
nullReadAfter: []int{0, 0},
|
nullReadAfter: []int{0, 0},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(128)}
|
||||||
Pool: buffer.NoPool(128),
|
r := cr(g, o)
|
||||||
ReadSize: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
p := make([]byte, 64)
|
p := make([]byte, 64)
|
||||||
n, err := r.Read(p)
|
n, err := r.Read(p)
|
||||||
if n != 0 || err != nil {
|
if n != 0 || err != nil {
|
||||||
@ -229,79 +207,17 @@ func TestRead(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("null read on non-empty", 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, 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) {
|
t.Run("read error without content", func(t *testing.T) {
|
||||||
g := &gen{
|
g := &gen{
|
||||||
max: 1 << 15,
|
max: 1 << 15,
|
||||||
errAfter: []int{32},
|
errAfter: []int{32},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(32)}
|
||||||
Pool: buffer.NoPool(128),
|
r := cr(g, o)
|
||||||
ReadSize: 32,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
p := make([]byte, 64)
|
p := make([]byte, 64)
|
||||||
n, err := r.Read(p)
|
n, err := r.Read(p)
|
||||||
if n != 64 || err != nil {
|
if n != 32 || err != nil {
|
||||||
t.Fatal("failed to read", n, err)
|
t.Fatal("failed to read", n, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,15 +234,11 @@ func TestRead(t *testing.T) {
|
|||||||
fastErr: true,
|
fastErr: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(32)}
|
||||||
Pool: buffer.NoPool(128),
|
r := cr(g, o)
|
||||||
ReadSize: 32,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
p := make([]byte, 64)
|
p := make([]byte, 64)
|
||||||
n, err := r.Read(p)
|
n, err := r.Read(p)
|
||||||
if n != 64 || err != nil {
|
if n != 32 || err != nil {
|
||||||
t.Fatal("failed to read", n, err)
|
t.Fatal("failed to read", n, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,13 +255,9 @@ func TestRead(t *testing.T) {
|
|||||||
fastErr: true,
|
fastErr: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(32)}
|
||||||
Pool: buffer.NoPool(128),
|
|
||||||
ReadSize: 32,
|
|
||||||
}
|
|
||||||
|
|
||||||
var result []byte
|
var result []byte
|
||||||
r := buffer.ReaderFrom(g, o)
|
r := cr(g, o)
|
||||||
p := make([]byte, 9)
|
p := make([]byte, 9)
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
if n, err := r.Read(p); n != 9 || err != nil {
|
if n, err := r.Read(p); n != 9 || err != nil {
|
||||||
@ -368,4 +276,5 @@ func TestRead(t *testing.T) {
|
|||||||
t.Fatal("invalid content")
|
t.Fatal("invalid content")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
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 TestReadBytes(t *testing.T) {
|
func TestReadBytes(t *testing.T) {
|
||||||
|
for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} {
|
||||||
|
t.Run(title, func(t *testing.T) {
|
||||||
t.Run("find", func(t *testing.T) {
|
t.Run("find", func(t *testing.T) {
|
||||||
g := &gen{
|
g := &gen{
|
||||||
max: 1 << 15,
|
max: 1 << 15,
|
||||||
@ -17,7 +19,7 @@ func TestReadBytes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, ok, err := r.ReadBytes([]byte("123"), 64)
|
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -35,7 +37,7 @@ func TestReadBytes(t *testing.T) {
|
|||||||
t.Run("find not", func(t *testing.T) {
|
t.Run("find not", 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)
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -53,12 +55,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
customContent: map[int][]byte{12: []byte("123")},
|
customContent: map[int][]byte{12: []byte("123")},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(14)}
|
||||||
Pool: buffer.NoPool(14),
|
r := cr(g, o)
|
||||||
ReadSize: 14,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -81,12 +79,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
customContent: map[int][]byte{6: d},
|
customContent: map[int][]byte{6: d},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(8)}
|
||||||
Pool: buffer.NoPool(8),
|
r := cr(g, o)
|
||||||
ReadSize: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes(d, 64)
|
b, ok, err := r.ReadBytes(d, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -103,12 +97,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("find not across segments", func(t *testing.T) {
|
t.Run("find not across segments", func(t *testing.T) {
|
||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(15)}
|
||||||
Pool: buffer.NoPool(15),
|
r := cr(g, o)
|
||||||
ReadSize: 15,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 24)
|
b, ok, err := r.ReadBytes([]byte("123"), 24)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -125,12 +115,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("find not across multiple segments", func(t *testing.T) {
|
t.Run("find not across multiple segments", func(t *testing.T) {
|
||||||
g := &gen{max: 1 << 15}
|
g := &gen{max: 1 << 15}
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(15)}
|
||||||
Pool: buffer.NoPool(15),
|
r := cr(g, o)
|
||||||
ReadSize: 15,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -153,7 +139,7 @@ func TestReadBytes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, ok, err := r.ReadBytes([]byte("8"), 64)
|
b, ok, err := r.ReadBytes([]byte("8"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -176,7 +162,7 @@ func TestReadBytes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, ok, err := r.ReadBytes([]byte("123"), 64)
|
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -202,7 +188,7 @@ func TestReadBytes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, ok, err := r.ReadBytes([]byte("123"), 16)
|
b, ok, err := r.ReadBytes([]byte("123"), 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -242,12 +228,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
customContent: map[int][]byte{7: []byte("12")},
|
customContent: map[int][]byte{7: []byte("12")},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(8)}
|
||||||
Pool: buffer.NoPool(8),
|
r := cr(g, o)
|
||||||
ReadSize: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -269,12 +251,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
customContent: map[int][]byte{7: []byte("1234567890")},
|
customContent: map[int][]byte{7: []byte("1234567890")},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(8)}
|
||||||
Pool: buffer.NoPool(8),
|
r := cr(g, o)
|
||||||
ReadSize: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("1234567890123"), 64)
|
b, ok, err := r.ReadBytes([]byte("1234567890123"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -299,12 +277,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(8)}
|
||||||
Pool: buffer.NoPool(8),
|
r := cr(g, o)
|
||||||
ReadSize: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 10)
|
b, ok, err := r.ReadBytes([]byte("123"), 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -347,12 +321,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(8)}
|
||||||
Pool: buffer.NoPool(8),
|
r := cr(g, o)
|
||||||
ReadSize: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("1234567890123"), 20)
|
b, ok, err := r.ReadBytes([]byte("1234567890123"), 20)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -393,7 +363,7 @@ func TestReadBytes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, ok, err := r.ReadBytes([]byte("123"), 14)
|
b, ok, err := r.ReadBytes([]byte("123"), 14)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -416,7 +386,7 @@ func TestReadBytes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, ok, err := r.ReadBytes([]byte("123"), 14)
|
b, ok, err := r.ReadBytes([]byte("123"), 14)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -444,6 +414,51 @@ func TestReadBytes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("null delimiter", func(t *testing.T) {
|
||||||
|
g := &gen{
|
||||||
|
max: 1 << 15,
|
||||||
|
customContentAfter: []int{12},
|
||||||
|
customContent: map[int][]byte{12: []byte("123")},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, ok, err := r.ReadBytes(nil, 64)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("failed to find delimiter")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) != 0 {
|
||||||
|
t.Fatal("failed to generate right content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("find not none consumed", func(t *testing.T) {
|
||||||
|
g := &gen{}
|
||||||
|
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
t.Fatal("failed to find delimiter")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) != 0 {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("reader only", func(t *testing.T) {
|
||||||
|
cr := buffer.BufferedReader
|
||||||
t.Run("error before found", func(t *testing.T) {
|
t.Run("error before found", func(t *testing.T) {
|
||||||
g := &gen{
|
g := &gen{
|
||||||
max: 1 << 15,
|
max: 1 << 15,
|
||||||
@ -452,12 +467,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
customContent: map[int][]byte{12: []byte("123")},
|
customContent: map[int][]byte{12: []byte("123")},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(8)}
|
||||||
Pool: buffer.NoPool(8),
|
r := cr(g, o)
|
||||||
ReadSize: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -494,12 +505,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
customContent: map[int][]byte{12: []byte("123")},
|
customContent: map[int][]byte{12: []byte("123")},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(8)}
|
||||||
Pool: buffer.NoPool(8),
|
r := cr(g, o)
|
||||||
ReadSize: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -549,12 +556,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
customContent: map[int][]byte{12: []byte("123")},
|
customContent: map[int][]byte{12: []byte("123")},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(8)}
|
||||||
Pool: buffer.NoPool(8),
|
r := cr(g, o)
|
||||||
ReadSize: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -595,29 +598,6 @@ func TestReadBytes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("null delimiter", func(t *testing.T) {
|
|
||||||
g := &gen{
|
|
||||||
max: 1 << 15,
|
|
||||||
customContentAfter: []int{12},
|
|
||||||
customContent: map[int][]byte{12: []byte("123")},
|
|
||||||
}
|
|
||||||
|
|
||||||
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes(nil, 64)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
t.Fatal("failed to find delimiter")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b) != 0 {
|
|
||||||
t.Fatal("failed to generate right content")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("null read", func(t *testing.T) {
|
t.Run("null read", func(t *testing.T) {
|
||||||
g := &gen{
|
g := &gen{
|
||||||
max: 1 << 15,
|
max: 1 << 15,
|
||||||
@ -626,12 +606,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
customContent: map[int][]byte{12: []byte("123")},
|
customContent: map[int][]byte{12: []byte("123")},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(8)}
|
||||||
Pool: buffer.NoPool(8),
|
r := cr(g, o)
|
||||||
ReadSize: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -665,12 +641,8 @@ func TestReadBytes(t *testing.T) {
|
|||||||
fastErr: true,
|
fastErr: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(256)}
|
||||||
Pool: buffer.NoPool(256),
|
r := cr(g, o)
|
||||||
ReadSize: 256,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -684,69 +656,5 @@ func TestReadBytes(t *testing.T) {
|
|||||||
t.Fatal("failed to generate right content")
|
t.Fatal("failed to generate right content")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("find not none consumed", func(t *testing.T) {
|
|
||||||
g := &gen{}
|
|
||||||
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, ok, err := r.ReadBytes([]byte("123"), 64)
|
|
||||||
if !errors.Is(err, io.EOF) {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
t.Fatal("failed to find delimiter")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b) != 0 {
|
|
||||||
t.Fatal("invalid content")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// conditions:
|
|
||||||
// - A0: error before read
|
|
||||||
// - A1: error during read
|
|
||||||
// - B0: allocation error
|
|
||||||
// - B1: read error
|
|
||||||
// - B2: eof
|
|
||||||
// - C0: error right before delimiter
|
|
||||||
// - C1: error during delimiter
|
|
||||||
// - C2: error right after full delimiter
|
|
||||||
// - D0: error before max
|
|
||||||
// - D1: error right at max
|
|
||||||
// - D2: error after max
|
|
||||||
// - E0: error at zero segment position
|
|
||||||
// - E1: error at not first segment boundary
|
|
||||||
// - E2: error in first segment
|
|
||||||
// - E3: error in subsequent segment
|
|
||||||
// - F0: error at zero data position
|
|
||||||
// - F1: error at not zero data position
|
|
||||||
// - F3: error at last data position
|
|
||||||
// - G0: less than max buffered
|
|
||||||
// - G1: exactly max buffered
|
|
||||||
// - G2: more than max buffered
|
|
||||||
// - H0: max at first segment start
|
|
||||||
// - H1: max at segment boundary
|
|
||||||
// - H2: max in first segment
|
|
||||||
// - H3: max in subsequent segment
|
|
||||||
// - I0: delimiter in newly read
|
|
||||||
// - I1: delimiter in buffered
|
|
||||||
// - J0: delimiter before max
|
|
||||||
// - J1: delimiter right up to max
|
|
||||||
// - J2: delimiter over max
|
|
||||||
// - J3: delimiter right after max
|
|
||||||
// - J4: delimiter after max
|
|
||||||
// - K0: delimiter at zero segment position
|
|
||||||
// - K1: delimiter at subsequent segment boundary
|
|
||||||
// - K2: delimiter within segment
|
|
||||||
// - L0: delimiter at zero data position
|
|
||||||
// - L1: delimiter at within buffered data
|
|
||||||
// - L2: delimiter at the end of buffered data
|
|
||||||
// - L3: partial delimiter at the end of buffered data
|
|
||||||
// - L4: delimiter right after buffered data
|
|
||||||
// - M0: delimiter found
|
|
||||||
// - M1: delimiter not found
|
|
||||||
//
|
|
||||||
// variations:
|
|
||||||
// - A0B0...
|
|
||||||
}
|
}
|
||||||
|
|||||||
68
reader.go
68
reader.go
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
133
readutf8_test.go
133
readutf8_test.go
@ -1,13 +1,15 @@
|
|||||||
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) {
|
||||||
|
for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} {
|
||||||
|
t.Run(title, func(t *testing.T) {
|
||||||
t.Run("read all after error", func(t *testing.T) {
|
t.Run("read all after error", func(t *testing.T) {
|
||||||
g := &gen{
|
g := &gen{
|
||||||
rng: utf8Range,
|
rng: utf8Range,
|
||||||
@ -16,7 +18,7 @@ func TestReadUTF8(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@ -60,7 +62,7 @@ func TestReadUTF8(t *testing.T) {
|
|||||||
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)
|
||||||
@ -82,7 +84,7 @@ func TestReadUTF8(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@ -105,7 +107,7 @@ func TestReadUTF8(t *testing.T) {
|
|||||||
|
|
||||||
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)
|
||||||
@ -120,6 +122,65 @@ func TestReadUTF8(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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 := cr(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)
|
||||||
|
if len(runes) != 0 || n != 0 || !errors.Is(err, io.EOF) {
|
||||||
|
t.Fatal("failed to read EOF", len(runes), n, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("immediate err", func(t *testing.T) {
|
||||||
|
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 n != 0 {
|
||||||
|
t.Fatal("unexpected read", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(runes) != 0 {
|
||||||
|
t.Fatal("unexpected content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("reader only", func(t *testing.T) {
|
||||||
|
cr := buffer.BufferedReader
|
||||||
t.Run("null read", func(t *testing.T) {
|
t.Run("null read", func(t *testing.T) {
|
||||||
const numRunes = 6
|
const numRunes = 6
|
||||||
nullReadAfter := len(string([]rune(string(utf8W2Range))[:numRunes]))
|
nullReadAfter := len(string([]rune(string(utf8W2Range))[:numRunes]))
|
||||||
@ -129,12 +190,8 @@ func TestReadUTF8(t *testing.T) {
|
|||||||
nullReadAfter: []int{nullReadAfter, nullReadAfter},
|
nullReadAfter: []int{nullReadAfter, nullReadAfter},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := buffer.Options{
|
o := buffer.Options{Pool: buffer.NoPool(nullReadAfter)}
|
||||||
Pool: buffer.NoPool(nullReadAfter),
|
r := cr(g, o)
|
||||||
ReadSize: nullReadAfter,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
runes, _, err := r.ReadUTF8(numRunes - 2) // -2 for min read in readUTF8
|
runes, _, err := r.ReadUTF8(numRunes - 2) // -2 for min read in readUTF8
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -174,59 +231,5 @@ func TestReadUTF8(t *testing.T) {
|
|||||||
t.Fatal("invalid content 3")
|
t.Fatal("invalid content 3")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
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)
|
|
||||||
if len(runes) != 0 || n != 0 || !errors.Is(err, io.EOF) {
|
|
||||||
t.Fatal("failed to read EOF", len(runes), n, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("immediate err", func(t *testing.T) {
|
|
||||||
g := &gen{rng: utf8Range}
|
|
||||||
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 {
|
|
||||||
t.Fatal("unexpected read", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(runes) != 0 {
|
|
||||||
t.Fatal("unexpected content")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
177
writeto_test.go
177
writeto_test.go
@ -1,18 +1,20 @@
|
|||||||
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) {
|
||||||
|
for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} {
|
||||||
|
t.Run(title, func(t *testing.T) {
|
||||||
t.Run("write out from zero", func(t *testing.T) {
|
t.Run("write out from zero", 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 b bytes.Buffer
|
var b bytes.Buffer
|
||||||
n, err := r.WriteTo(&b)
|
n, err := r.WriteTo(&b)
|
||||||
@ -20,11 +22,11 @@ func TestWriteTo(t *testing.T) {
|
|||||||
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")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -32,7 +34,7 @@ func TestWriteTo(t *testing.T) {
|
|||||||
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 {
|
||||||
@ -53,7 +55,7 @@ func TestWriteTo(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if n64 != 1 << 15 - 256 {
|
if n64 != 1<<15-256 {
|
||||||
t.Fatal("write count")
|
t.Fatal("write count")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +67,7 @@ func TestWriteTo(t *testing.T) {
|
|||||||
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)
|
||||||
@ -90,47 +92,10 @@ func TestWriteTo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("after err", func(t *testing.T) {
|
|
||||||
g := &gen{
|
|
||||||
max: 1 << 15,
|
|
||||||
errAfter: []int{256},
|
|
||||||
fastErr: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
o := buffer.Options{
|
|
||||||
Pool: buffer.NoPool(64),
|
|
||||||
ReadSize: 16,
|
|
||||||
}
|
|
||||||
|
|
||||||
r := buffer.ReaderFrom(g, o)
|
|
||||||
b, err := r.Peek(512)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(b, generate(256)) {
|
|
||||||
t.Fatal("invalid content", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
n, err := r.WriteTo(&buf)
|
|
||||||
if !errors.Is(err, errTest) {
|
|
||||||
t.Fatal("failed to test with the right error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if n != 256 {
|
|
||||||
t.Fatal("write count")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(buf.Bytes(), generate(256)) {
|
|
||||||
t.Fatal("invalid content", 2)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("after eof empty", func(t *testing.T) {
|
t.Run("after eof empty", 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)
|
||||||
p := make([]byte, 512)
|
p := make([]byte, 512)
|
||||||
n, err := r.Read(p)
|
n, err := r.Read(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -160,6 +125,81 @@ func TestWriteTo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("write error", func(t *testing.T) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < 256 {
|
||||||
|
t.Fatal("not enough written")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(w.written, generate(1 << 15)[:n]) {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("short write", func(t *testing.T) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < 256 {
|
||||||
|
t.Fatal("not enough written")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(w.written, generate(1 << 15)[:n]) {
|
||||||
|
t.Fatal("invalid content")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("reader only", func(t *testing.T) {
|
||||||
|
cr := buffer.BufferedReader
|
||||||
|
t.Run("after err", func(t *testing.T) {
|
||||||
|
g := &gen{
|
||||||
|
max: 1 << 15,
|
||||||
|
errAfter: []int{256},
|
||||||
|
fastErr: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
o := buffer.Options{Pool: buffer.NoPool(64)}
|
||||||
|
r := cr(g, o)
|
||||||
|
b, err := r.Peek(512)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(b, generate(256)) {
|
||||||
|
t.Fatal("invalid content", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
n, err := r.WriteTo(&buf)
|
||||||
|
if !errors.Is(err, errTest) {
|
||||||
|
t.Fatal("failed to test with the right error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != 256 {
|
||||||
|
t.Fatal("write count")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(buf.Bytes(), generate(256)) {
|
||||||
|
t.Fatal("invalid content", 2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("after error empty", func(t *testing.T) {
|
t.Run("after error empty", func(t *testing.T) {
|
||||||
g := &gen{
|
g := &gen{
|
||||||
max: 1 << 15,
|
max: 1 << 15,
|
||||||
@ -167,8 +207,8 @@ func TestWriteTo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, 1 << 15)
|
p := make([]byte, 1<<15)
|
||||||
n, err := r.Read(p)
|
n, err := r.Read(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -204,7 +244,7 @@ func TestWriteTo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@ -228,7 +268,7 @@ func TestWriteTo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@ -244,42 +284,5 @@ func TestWriteTo(t *testing.T) {
|
|||||||
t.Fatal("invalid content")
|
t.Fatal("invalid content")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("write error", func(t *testing.T) {
|
|
||||||
g := &gen{max: 1 << 15}
|
|
||||||
o := buffer.Options{Pool: buffer.NoPool(1 << 12)}
|
|
||||||
r := buffer.ReaderFrom(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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if n < 256 {
|
|
||||||
t.Fatal("not enough written")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(w.written, generate(1 << 15)[:n]) {
|
|
||||||
t.Fatal("invalid content")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("short write", func(t *testing.T) {
|
|
||||||
g := &gen{max: 1 << 15}
|
|
||||||
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 {
|
|
||||||
t.Fatal("not enough written")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(w.written, generate(1 << 15)[:n]) {
|
|
||||||
t.Fatal("invalid content")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user