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