Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bytes/reader_test #75

Merged
merged 1 commit into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}