blob: df5eedb8ff067ee656fc0ffc8f029f6811b96fdb [file] [log] [blame]
// Copyright 2015 Google LLC
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gensupport
import (
"bytes"
"io"
"io/ioutil"
"reflect"
"testing"
"testing/iotest"
"google.golang.org/api/googleapi"
)
// getChunkAsString reads a chunk from mb, but does not call Next.
func getChunkAsString(t *testing.T, mb *MediaBuffer) (string, error) {
chunk, _, size, err := mb.Chunk()
buf, e := ioutil.ReadAll(chunk)
if e != nil {
t.Fatalf("Failed reading chunk: %v", e)
}
if size != len(buf) {
t.Fatalf("reported chunk size doesn't match actual chunk size: got: %v; want: %v", size, len(buf))
}
return string(buf), err
}
func TestChunking(t *testing.T) {
type testCase struct {
data string // the data to read from the Reader
finalErr error // error to return after data has been read
chunkSize int
wantChunks []string
}
for _, singleByteReads := range []bool{true, false} {
for _, tc := range []testCase{
{
data: "abcdefg",
finalErr: nil,
chunkSize: 3,
wantChunks: []string{"abc", "def", "g"},
},
{
data: "abcdefg",
finalErr: nil,
chunkSize: 1,
wantChunks: []string{"a", "b", "c", "d", "e", "f", "g"},
},
{
data: "abcdefg",
finalErr: nil,
chunkSize: 7,
wantChunks: []string{"abcdefg"},
},
{
data: "abcdefg",
finalErr: nil,
chunkSize: 8,
wantChunks: []string{"abcdefg"},
},
{
data: "abcdefg",
finalErr: io.ErrUnexpectedEOF,
chunkSize: 3,
wantChunks: []string{"abc", "def", "g"},
},
{
data: "abcdefg",
finalErr: io.ErrUnexpectedEOF,
chunkSize: 8,
wantChunks: []string{"abcdefg"},
},
} {
var r io.Reader = &errReader{buf: []byte(tc.data), err: tc.finalErr}
if singleByteReads {
r = iotest.OneByteReader(r)
}
mb := NewMediaBuffer(r, tc.chunkSize)
var gotErr error
got := []string{}
for {
chunk, err := getChunkAsString(t, mb)
if len(chunk) != 0 {
got = append(got, string(chunk))
}
if err != nil {
gotErr = err
break
}
mb.Next()
}
if !reflect.DeepEqual(got, tc.wantChunks) {
t.Errorf("Failed reading buffer: got: %v; want:%v", got, tc.wantChunks)
}
expectedErr := tc.finalErr
if expectedErr == nil {
expectedErr = io.EOF
}
if gotErr != expectedErr {
t.Errorf("Reading buffer error: got: %v; want: %v", gotErr, expectedErr)
}
}
}
}
func TestChunkCanBeReused(t *testing.T) {
er := &errReader{buf: []byte("abcdefg")}
mb := NewMediaBuffer(er, 3)
// expectChunk reads a chunk and checks that it got what was wanted.
expectChunk := func(want string, wantErr error) {
got, err := getChunkAsString(t, mb)
if err != wantErr {
t.Errorf("error reading buffer: got: %v; want: %v", err, wantErr)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("Failed reading buffer: got: %q; want:%q", got, want)
}
}
expectChunk("abc", nil)
// On second call, should get same chunk again.
expectChunk("abc", nil)
mb.Next()
expectChunk("def", nil)
expectChunk("def", nil)
mb.Next()
expectChunk("g", io.EOF)
expectChunk("g", io.EOF)
mb.Next()
expectChunk("", io.EOF)
}
func TestPos(t *testing.T) {
er := &errReader{buf: []byte("abcdefg")}
mb := NewMediaBuffer(er, 3)
expectChunkAtOffset := func(want int64, wantErr error) {
_, off, _, err := mb.Chunk()
if err != wantErr {
t.Errorf("error reading buffer: got: %v; want: %v", err, wantErr)
}
if got := off; got != want {
t.Errorf("resumable buffer Pos: got: %v; want: %v", got, want)
}
}
// We expect the first chunk to be at offset 0.
expectChunkAtOffset(0, nil)
// Fetching the same chunk should return the same offset.
expectChunkAtOffset(0, nil)
// Calling Next multiple times should only cause off to advance by 3, since off is not advanced until
// the chunk is actually read.
mb.Next()
mb.Next()
expectChunkAtOffset(3, nil)
mb.Next()
// Load the final 1-byte chunk.
expectChunkAtOffset(6, io.EOF)
// Next will advance 1 byte. But there are no more chunks, so off will not increase beyond 7.
mb.Next()
expectChunkAtOffset(7, io.EOF)
mb.Next()
expectChunkAtOffset(7, io.EOF)
}
// bytes.Reader implements both Reader and ReaderAt. The following types
// implement various combinations of Reader, ReaderAt and ContentTyper, by
// wrapping bytes.Reader. All implement at least ReaderAt, so they can be
// passed to ReaderAtToReader. The following table summarizes which types
// implement which interfaces:
//
// ReaderAt Reader ContentTyper
// reader x x
// typerReader x x x
// readerAt x
// typerReaderAt x x
// reader implements Reader, in addition to ReaderAt.
type reader struct {
r *bytes.Reader
}
func (r *reader) ReadAt(b []byte, off int64) (n int, err error) {
return r.r.ReadAt(b, off)
}
func (r *reader) Read(b []byte) (n int, err error) {
return r.r.Read(b)
}
// typerReader implements Reader and ContentTyper, in addition to ReaderAt.
type typerReader struct {
r *bytes.Reader
}
func (tr *typerReader) ReadAt(b []byte, off int64) (n int, err error) {
return tr.r.ReadAt(b, off)
}
func (tr *typerReader) Read(b []byte) (n int, err error) {
return tr.r.Read(b)
}
func (tr *typerReader) ContentType() string {
return "ctype"
}
// readerAt implements only ReaderAt.
type readerAt struct {
r *bytes.Reader
}
func (ra *readerAt) ReadAt(b []byte, off int64) (n int, err error) {
return ra.r.ReadAt(b, off)
}
// typerReaderAt implements ContentTyper, in addition to ReaderAt.
type typerReaderAt struct {
r *bytes.Reader
}
func (tra *typerReaderAt) ReadAt(b []byte, off int64) (n int, err error) {
return tra.r.ReadAt(b, off)
}
func (tra *typerReaderAt) ContentType() string {
return "ctype"
}
func TestAdapter(t *testing.T) {
data := "abc"
checkConversion := func(to io.Reader, wantTyper bool) {
if _, ok := to.(googleapi.ContentTyper); ok != wantTyper {
t.Errorf("reader implements typer? got: %v; want: %v", ok, wantTyper)
}
if typer, ok := to.(googleapi.ContentTyper); ok && typer.ContentType() != "ctype" {
t.Errorf("content type: got: %s; want: ctype", typer.ContentType())
}
buf, err := ioutil.ReadAll(to)
if err != nil {
t.Errorf("error reading data: %v", err)
return
}
if !bytes.Equal(buf, []byte(data)) {
t.Errorf("failed reading data: got: %s; want: %s", buf, data)
}
}
type testCase struct {
from io.ReaderAt
wantTyper bool
}
for _, tc := range []testCase{
{
from: &reader{bytes.NewReader([]byte(data))},
wantTyper: false,
},
{
// Reader and ContentTyper
from: &typerReader{bytes.NewReader([]byte(data))},
wantTyper: true,
},
{
// ReaderAt
from: &readerAt{bytes.NewReader([]byte(data))},
wantTyper: false,
},
{
// ReaderAt and ContentTyper
from: &typerReaderAt{bytes.NewReader([]byte(data))},
wantTyper: true,
},
} {
to := ReaderAtToReader(tc.from, int64(len(data)))
checkConversion(to, tc.wantTyper)
// tc.from is a ReaderAt, and should be treated like one, even
// if it also implements Reader. Specifically, it can be
// reused and read from the beginning.
to = ReaderAtToReader(tc.from, int64(len(data)))
checkConversion(to, tc.wantTyper)
}
}