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