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