Skip to content

Commit

Permalink
bytes/reader_test
Browse files Browse the repository at this point in the history
  • Loading branch information
xushiwei committed Sep 11, 2023
1 parent 6524595 commit 4e4dd4d
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 20 deletions.
42 changes: 22 additions & 20 deletions bytes/bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,21 @@ type Reader struct {

// NewReader create a readonly stream for byte slice:
//
// var slice []byte
// ...
// r := bytes.NewReader(slice)
// ...
// r.Seek(0, 0) // r.SeekToBegin()
// ...
// var slice []byte
// ...
// r := bytes.NewReader(slice)
// ...
// r.Seek(0, 0) // r.SeekToBegin()
// ...
//
// Unlike the standard library's bytes.Reader, the returned Reader supports Seek.
func NewReader(val []byte) *Reader {
return &Reader{val, 0}
}

// Size returns the original length of the underlying byte slice.
func (r *Reader) Size() int64 { return int64(len(r.b)) }

func (r *Reader) Len() int {
if r.off >= len(r.b) {
return 0
Expand All @@ -61,10 +64,10 @@ func (r *Reader) SeekToBegin() (err error) {

func (r *Reader) Seek(offset int64, whence int) (ret int64, err error) {
switch whence {
case 0:
case 1:
case io.SeekStart:
case io.SeekCurrent:
offset += int64(r.off)
case 2:
case io.SeekEnd:
offset += int64(len(r.b))
default:
err = syscall.EINVAL
Expand Down Expand Up @@ -107,10 +110,10 @@ type Writer struct {

// NewWriter NewWriter creates a write stream with a limited capacity:
//
// slice := make([]byte, 1024)
// w := bytes.NewWriter(slice)
// ...
// writtenData := w.Bytes()
// slice := make([]byte, 1024)
// w := bytes.NewWriter(slice)
// ...
// writtenData := w.Bytes()
//
// If we write more than 1024 bytes of data to w, the excess data will be discarded.
func NewWriter(buff []byte) *Writer {
Expand Down Expand Up @@ -149,13 +152,12 @@ type Buffer struct {
// NewBuffer creates a random read/write memory file that supports ReadAt/WriteAt
// methods instead of Read/Write:
//
// b := bytes.NewBuffer()
// b.Truncate(100)
// b.WriteAt([]byte("hello"), 100)
// slice := make([]byte, 105)
// n, err := b.ReadAt(slice, 0)
// ...
//
// b := bytes.NewBuffer()
// b.Truncate(100)
// b.WriteAt([]byte("hello"), 100)
// slice := make([]byte, 105)
// n, err := b.ReadAt(slice, 0)
// ...
func NewBuffer() *Buffer {
return new(Buffer)
}
Expand Down
178 changes: 178 additions & 0 deletions bytes/reader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package bytes_test

import (
"io"
"sync"
"syscall"
"testing"

. "github.com/qiniu/x/bytes"
)

func TestReader(t *testing.T) {
r := NewReader([]byte("0123456789"))
tests := []struct {
off int64
seek int
n int
want string
wantpos int64
readerr error
seekerr string
}{
{seek: io.SeekStart, off: 0, n: 20, want: "0123456789"},
{seek: io.SeekStart, off: 1, n: 1, want: "1"},
{seek: io.SeekCurrent, off: 1, wantpos: 3, n: 2, want: "34"},
{seek: io.SeekStart, off: -1, seekerr: "invalid argument"},
{seek: io.SeekStart, off: 1 << 33, wantpos: 10, readerr: nil},
{seek: io.SeekCurrent, off: 1, wantpos: 10, readerr: nil},
{seek: io.SeekStart, n: 5, want: "01234"},
{seek: io.SeekCurrent, n: 5, want: "56789"},
{seek: io.SeekEnd, off: -1, n: 1, wantpos: 9, want: "9"},
}

for i, tt := range tests {
pos, err := r.Seek(tt.off, tt.seek)
if err == nil && tt.seekerr != "" {
t.Errorf("%d. want seek error %q", i, tt.seekerr)
continue
}
if err != nil && err.Error() != tt.seekerr {
t.Errorf("%d. seek error = %q; want %q", i, err.Error(), tt.seekerr)
continue
}
if tt.wantpos != 0 && tt.wantpos != pos {
t.Errorf("%d. pos = %d, want %d", i, pos, tt.wantpos)
}
buf := make([]byte, tt.n)
n, err := r.Read(buf)
if err != tt.readerr {
t.Errorf("%d. read = %v; want %v", i, err, tt.readerr)
continue
}
got := string(buf[:n])
if got != tt.want {
t.Errorf("%d. got %q; want %q", i, got, tt.want)
}
}
}

func TestReadAfterBigSeek(t *testing.T) {
r := NewReader([]byte("0123456789"))
if _, err := r.Seek(1<<31+5, io.SeekStart); err != nil {
t.Fatal(err)
}
if n, err := r.Read(make([]byte, 10)); n != 0 || err != io.EOF {
t.Errorf("Read = %d, %v; want 0, EOF", n, err)
}
}

func TestEmptyReaderConcurrent(t *testing.T) {
// Test for the race detector, to verify a Read that doesn't yield any bytes
// is okay to use from multiple goroutines. This was our historic behavior.
// See golang.org/issue/7856
r := NewReader([]byte{})
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(2)
go func() {
defer wg.Done()
var buf [1]byte
r.Read(buf[:])
}()
go func() {
defer wg.Done()
r.Read(nil)
}()
}
wg.Wait()
}

func TestReaderLen(t *testing.T) {
const data = "hello world"
r := NewReader([]byte(data))
if got, want := r.Len(), 11; got != want {
t.Errorf("r.Len(): got %d, want %d", got, want)
}
if n, err := r.Read(make([]byte, 10)); err != nil || n != 10 {
t.Errorf("Read failed: read %d %v", n, err)
}
if got, want := r.Len(), 1; got != want {
t.Errorf("r.Len(): got %d, want %d", got, want)
}
if n, err := r.Read(make([]byte, 1)); err != nil || n != 1 {
t.Errorf("Read failed: read %d %v; want 1, nil", n, err)
}
if got, want := r.Len(), 0; got != want {
t.Errorf("r.Len(): got %d, want %d", got, want)
}
}

// verify that copying from an empty reader always has the same results,
// regardless of the presence of a WriteTo method.
func TestReaderCopyNothing(t *testing.T) {
type nErr struct {
n int64
err error
}
type justReader struct {
io.Reader
}
type justWriter struct {
io.Writer
}
discard := justWriter{io.Discard} // hide ReadFrom

var with, withOut nErr
with.n, with.err = io.Copy(discard, NewReader(nil))
withOut.n, withOut.err = io.Copy(discard, justReader{NewReader(nil)})
if with != withOut {
t.Errorf("behavior differs: with = %#v; without: %#v", with, withOut)
}
}

// tests that Len is affected by reads, but Size is not.
func TestReaderLenSize(t *testing.T) {
r := NewReader([]byte("abc"))
io.CopyN(io.Discard, r, 1)
if r.Len() != 2 {
t.Errorf("Len = %d; want 2", r.Len())
}
if r.Size() != 3 {
t.Errorf("Size = %d; want 3", r.Size())
}
}

func TestReaderZero(t *testing.T) {
if l := (&Reader{}).Len(); l != 0 {
t.Errorf("Len: got %d, want 0", l)
}

if e := (&Reader{}).SeekToBegin(); e != nil {
t.Errorf("SeekToBegin: got %v, want nil", e)
}

if _, e := (&Reader{}).Seek(0, 4); e != syscall.EINVAL {
t.Errorf("SeekToBegin: got %v, want EINVAL", e)
}

if e := (&Reader{}).Close(); e != nil {
t.Errorf("Close: got %v, want nil", e)
}

if v := (&Reader{}).Bytes(); v != nil {
t.Errorf("Bytes: got %v, want nil", nil)
}

if n, err := (&Reader{}).Read(nil); n != 0 || err != nil {
t.Errorf("Read: got %d, %v; want 0, io.EOF", n, err)
}

if offset, err := (&Reader{}).Seek(11, io.SeekStart); offset != 0 || err != nil {
t.Errorf("Seek: got %d, %v; want 11, nil", offset, err)
}

if s := (&Reader{}).Size(); s != 0 {
t.Errorf("Size: got %d, want 0", s)
}
}

0 comments on commit 4e4dd4d

Please sign in to comment.