// 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) } }