Switch to using the dep tool and update all the dependencies

This commit is contained in:
Nick Craig-Wood
2017-05-11 15:39:54 +01:00
parent 5135ff73cb
commit 98c2d2c41b
5321 changed files with 4483197 additions and 5918 deletions
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gensupport
import (
"testing"
"time"
)
func TestBackoff(t *testing.T) {
eb := &ExponentialBackoff{Base: time.Millisecond, Max: time.Second}
var total time.Duration
for n, max := 0, 2*time.Millisecond; ; n, max = n+1, max*2 {
if n > 100 {
// There's less than 1 in 10^28 of taking longer than 100 iterations,
// so this is just to check we don't have an infinite loop.
t.Fatalf("Failed to timeout after 100 iterations.")
}
pause, retry := eb.Pause()
if !retry {
break
}
if 0 > pause || pause >= max {
t.Errorf("Iteration %d: pause = %v; want in range [0, %v)", n, pause, max)
}
total += pause
}
if total < time.Second {
t.Errorf("Total time = %v; want > %v", total, time.Second)
}
}
+296
View File
@@ -0,0 +1,296 @@
// Copyright 2015 Google Inc. All rights reserved.
// 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)
}
}
+28
View File
@@ -0,0 +1,28 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gensupport
import (
"fmt"
"runtime"
"strings"
"testing"
)
func TestGoogleClientHeader(t *testing.T) {
const genVersion = "20170101"
gv := strings.Replace(runtime.Version(), " ", "_", -1)
got := GoogleClientHeader(genVersion, "gccl/xyz")
want := fmt.Sprintf("gl-go/%s gccl/xyz gdcl/%s", gv, genVersion)
if got != want {
t.Errorf("got %q, want %q", got, want)
}
got = GoogleClientHeader(genVersion, "")
want = fmt.Sprintf("gl-go/%s gdcl/%s", gv, genVersion)
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
+489
View File
@@ -0,0 +1,489 @@
// Copyright 2015 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gensupport
import (
"encoding/json"
"reflect"
"testing"
"google.golang.org/api/googleapi"
)
type schema struct {
// Basic types
B bool `json:"b,omitempty"`
F float64 `json:"f,omitempty"`
I int64 `json:"i,omitempty"`
Istr int64 `json:"istr,omitempty,string"`
Str string `json:"str,omitempty"`
// Pointers to basic types
PB *bool `json:"pb,omitempty"`
PF *float64 `json:"pf,omitempty"`
PI *int64 `json:"pi,omitempty"`
PIStr *int64 `json:"pistr,omitempty,string"`
PStr *string `json:"pstr,omitempty"`
// Other types
Int64s googleapi.Int64s `json:"i64s,omitempty"`
S []int `json:"s,omitempty"`
M map[string]string `json:"m,omitempty"`
Any interface{} `json:"any,omitempty"`
Child *child `json:"child,omitempty"`
MapToAnyArray map[string][]interface{} `json:"maptoanyarray,omitempty"`
ForceSendFields []string `json:"-"`
NullFields []string `json:"-"`
}
type child struct {
B bool `json:"childbool,omitempty"`
}
type testCase struct {
s schema
want string
}
func TestBasics(t *testing.T) {
for _, tc := range []testCase{
{
s: schema{},
want: `{}`,
},
{
s: schema{
ForceSendFields: []string{"B", "F", "I", "Istr", "Str", "PB", "PF", "PI", "PIStr", "PStr"},
},
want: `{"b":false,"f":0.0,"i":0,"istr":"0","str":""}`,
},
{
s: schema{
NullFields: []string{"B", "F", "I", "Istr", "Str", "PB", "PF", "PI", "PIStr", "PStr"},
},
want: `{"b":null,"f":null,"i":null,"istr":null,"str":null,"pb":null,"pf":null,"pi":null,"pistr":null,"pstr":null}`,
},
{
s: schema{
B: true,
F: 1.2,
I: 1,
Istr: 2,
Str: "a",
PB: googleapi.Bool(true),
PF: googleapi.Float64(1.2),
PI: googleapi.Int64(int64(1)),
PIStr: googleapi.Int64(int64(2)),
PStr: googleapi.String("a"),
},
want: `{"b":true,"f":1.2,"i":1,"istr":"2","str":"a","pb":true,"pf":1.2,"pi":1,"pistr":"2","pstr":"a"}`,
},
{
s: schema{
B: false,
F: 0.0,
I: 0,
Istr: 0,
Str: "",
PB: googleapi.Bool(false),
PF: googleapi.Float64(0.0),
PI: googleapi.Int64(int64(0)),
PIStr: googleapi.Int64(int64(0)),
PStr: googleapi.String(""),
},
want: `{"pb":false,"pf":0.0,"pi":0,"pistr":"0","pstr":""}`,
},
{
s: schema{
B: false,
F: 0.0,
I: 0,
Istr: 0,
Str: "",
PB: googleapi.Bool(false),
PF: googleapi.Float64(0.0),
PI: googleapi.Int64(int64(0)),
PIStr: googleapi.Int64(int64(0)),
PStr: googleapi.String(""),
ForceSendFields: []string{"B", "F", "I", "Istr", "Str", "PB", "PF", "PI", "PIStr", "PStr"},
},
want: `{"b":false,"f":0.0,"i":0,"istr":"0","str":"","pb":false,"pf":0.0,"pi":0,"pistr":"0","pstr":""}`,
},
{
s: schema{
B: false,
F: 0.0,
I: 0,
Istr: 0,
Str: "",
PB: googleapi.Bool(false),
PF: googleapi.Float64(0.0),
PI: googleapi.Int64(int64(0)),
PIStr: googleapi.Int64(int64(0)),
PStr: googleapi.String(""),
NullFields: []string{"B", "F", "I", "Istr", "Str"},
},
want: `{"b":null,"f":null,"i":null,"istr":null,"str":null,"pb":false,"pf":0.0,"pi":0,"pistr":"0","pstr":""}`,
},
} {
checkMarshalJSON(t, tc)
}
}
func TestSliceFields(t *testing.T) {
for _, tc := range []testCase{
{
s: schema{},
want: `{}`,
},
{
s: schema{S: []int{}, Int64s: googleapi.Int64s{}},
want: `{}`,
},
{
s: schema{S: []int{1}, Int64s: googleapi.Int64s{1}},
want: `{"s":[1],"i64s":["1"]}`,
},
{
s: schema{
ForceSendFields: []string{"S", "Int64s"},
},
want: `{"s":[],"i64s":[]}`,
},
{
s: schema{
S: []int{},
Int64s: googleapi.Int64s{},
ForceSendFields: []string{"S", "Int64s"},
},
want: `{"s":[],"i64s":[]}`,
},
{
s: schema{
S: []int{1},
Int64s: googleapi.Int64s{1},
ForceSendFields: []string{"S", "Int64s"},
},
want: `{"s":[1],"i64s":["1"]}`,
},
{
s: schema{
NullFields: []string{"S", "Int64s"},
},
want: `{"s":null,"i64s":null}`,
},
} {
checkMarshalJSON(t, tc)
}
}
func TestMapField(t *testing.T) {
for _, tc := range []testCase{
{
s: schema{},
want: `{}`,
},
{
s: schema{M: make(map[string]string)},
want: `{}`,
},
{
s: schema{M: map[string]string{"a": "b"}},
want: `{"m":{"a":"b"}}`,
},
{
s: schema{
ForceSendFields: []string{"M"},
},
want: `{"m":{}}`,
},
{
s: schema{
NullFields: []string{"M"},
},
want: `{"m":null}`,
},
{
s: schema{
M: make(map[string]string),
ForceSendFields: []string{"M"},
},
want: `{"m":{}}`,
},
{
s: schema{
M: make(map[string]string),
NullFields: []string{"M"},
},
want: `{"m":null}`,
},
{
s: schema{
M: map[string]string{"a": "b"},
ForceSendFields: []string{"M"},
},
want: `{"m":{"a":"b"}}`,
},
} {
checkMarshalJSON(t, tc)
}
}
func TestMapToAnyArray(t *testing.T) {
for _, tc := range []testCase{
{
s: schema{},
want: `{}`,
},
{
s: schema{MapToAnyArray: make(map[string][]interface{})},
want: `{}`,
},
{
s: schema{
MapToAnyArray: map[string][]interface{}{
"a": []interface{}{2, "b"},
},
},
want: `{"maptoanyarray":{"a":[2, "b"]}}`,
},
{
s: schema{
MapToAnyArray: map[string][]interface{}{
"a": nil,
},
},
want: `{"maptoanyarray":{"a": null}}`,
},
{
s: schema{
MapToAnyArray: map[string][]interface{}{
"a": []interface{}{nil},
},
},
want: `{"maptoanyarray":{"a":[null]}}`,
},
{
s: schema{
ForceSendFields: []string{"MapToAnyArray"},
},
want: `{"maptoanyarray":{}}`,
},
{
s: schema{
NullFields: []string{"MapToAnyArray"},
},
want: `{"maptoanyarray":null}`,
},
{
s: schema{
MapToAnyArray: make(map[string][]interface{}),
ForceSendFields: []string{"MapToAnyArray"},
},
want: `{"maptoanyarray":{}}`,
},
{
s: schema{
MapToAnyArray: map[string][]interface{}{
"a": []interface{}{2, "b"},
},
ForceSendFields: []string{"MapToAnyArray"},
},
want: `{"maptoanyarray":{"a":[2, "b"]}}`,
},
} {
checkMarshalJSON(t, tc)
}
}
type anyType struct {
Field int
}
func (a anyType) MarshalJSON() ([]byte, error) {
return []byte(`"anyType value"`), nil
}
func TestAnyField(t *testing.T) {
// ForceSendFields has no effect on nil interfaces and interfaces that contain nil pointers.
var nilAny *anyType
for _, tc := range []testCase{
{
s: schema{},
want: `{}`,
},
{
s: schema{Any: nilAny},
want: `{"any": null}`,
},
{
s: schema{Any: &anyType{}},
want: `{"any":"anyType value"}`,
},
{
s: schema{Any: anyType{}},
want: `{"any":"anyType value"}`,
},
{
s: schema{
ForceSendFields: []string{"Any"},
},
want: `{}`,
},
{
s: schema{
NullFields: []string{"Any"},
},
want: `{"any":null}`,
},
{
s: schema{
Any: nilAny,
ForceSendFields: []string{"Any"},
},
want: `{"any": null}`,
},
{
s: schema{
Any: &anyType{},
ForceSendFields: []string{"Any"},
},
want: `{"any":"anyType value"}`,
},
{
s: schema{
Any: anyType{},
ForceSendFields: []string{"Any"},
},
want: `{"any":"anyType value"}`,
},
} {
checkMarshalJSON(t, tc)
}
}
func TestSubschema(t *testing.T) {
// Subschemas are always stored as pointers, so ForceSendFields has no effect on them.
for _, tc := range []testCase{
{
s: schema{},
want: `{}`,
},
{
s: schema{
ForceSendFields: []string{"Child"},
},
want: `{}`,
},
{
s: schema{
NullFields: []string{"Child"},
},
want: `{"child":null}`,
},
{
s: schema{Child: &child{}},
want: `{"child":{}}`,
},
{
s: schema{
Child: &child{},
ForceSendFields: []string{"Child"},
},
want: `{"child":{}}`,
},
{
s: schema{Child: &child{B: true}},
want: `{"child":{"childbool":true}}`,
},
{
s: schema{
Child: &child{B: true},
ForceSendFields: []string{"Child"},
},
want: `{"child":{"childbool":true}}`,
},
} {
checkMarshalJSON(t, tc)
}
}
// checkMarshalJSON verifies that calling schemaToMap on tc.s yields a result which is equivalent to tc.want.
func checkMarshalJSON(t *testing.T, tc testCase) {
doCheckMarshalJSON(t, tc.s, tc.s.ForceSendFields, tc.s.NullFields, tc.want)
if len(tc.s.ForceSendFields) == 0 && len(tc.s.NullFields) == 0 {
// verify that the code path used when ForceSendFields and NullFields
// are non-empty produces the same output as the fast path that is used
// when they are empty.
doCheckMarshalJSON(t, tc.s, []string{"dummy"}, []string{"dummy"}, tc.want)
}
}
func doCheckMarshalJSON(t *testing.T, s schema, forceSendFields, nullFields []string, wantJSON string) {
encoded, err := MarshalJSON(s, forceSendFields, nullFields)
if err != nil {
t.Fatalf("encoding json:\n got err: %v", err)
}
// The expected and obtained JSON can differ in field ordering, so unmarshal before comparing.
var got interface{}
var want interface{}
err = json.Unmarshal(encoded, &got)
if err != nil {
t.Fatalf("decoding json:\n got err: %v", err)
}
err = json.Unmarshal([]byte(wantJSON), &want)
if err != nil {
t.Fatalf("decoding json:\n got err: %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("schemaToMap:\ngot :%v\nwant: %v", got, want)
}
}
func TestParseJSONTag(t *testing.T) {
for _, tc := range []struct {
tag string
want jsonTag
}{
{
tag: "-",
want: jsonTag{ignore: true},
}, {
tag: "name,omitempty",
want: jsonTag{apiName: "name"},
}, {
tag: "name,omitempty,string",
want: jsonTag{apiName: "name", stringFormat: true},
},
} {
got, err := parseJSONTag(tc.tag)
if err != nil {
t.Fatalf("parsing json:\n got err: %v\ntag: %q", err, tc.tag)
}
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("parseJSONTage:\ngot :%s\nwant:%s", got, tc.want)
}
}
}
func TestParseMalformedJSONTag(t *testing.T) {
for _, tag := range []string{
"",
"name",
"name,",
"name,blah",
"name,blah,string",
",omitempty",
",omitempty,string",
"name,omitempty,string,blah",
} {
_, err := parseJSONTag(tag)
if err == nil {
t.Fatalf("parsing json: expected err, got nil for tag: %v", tag)
}
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gensupport
import (
"encoding/json"
"math"
"testing"
)
func TestJSONFloat(t *testing.T) {
for _, test := range []struct {
in string
want float64
}{
{"0", 0},
{"-10", -10},
{"1e23", 1e23},
{`"Infinity"`, math.Inf(1)},
{`"-Infinity"`, math.Inf(-1)},
{`"NaN"`, math.NaN()},
} {
var f64 JSONFloat64
if err := json.Unmarshal([]byte(test.in), &f64); err != nil {
t.Fatal(err)
}
got := float64(f64)
if got != test.want && math.IsNaN(got) != math.IsNaN(test.want) {
t.Errorf("%s: got %f, want %f", test.in, got, test.want)
}
}
}
func TestJSONFloatErrors(t *testing.T) {
var f64 JSONFloat64
for _, in := range []string{"", "a", `"Inf"`, `"-Inf"`, `"nan"`, `"nana"`} {
if err := json.Unmarshal([]byte(in), &f64); err == nil {
t.Errorf("%q: got nil, want error", in)
}
}
}
+142
View File
@@ -0,0 +1,142 @@
// Copyright 2015 Google Inc. All rights reserved.
// 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"
)
func TestContentSniffing(t *testing.T) {
type testCase struct {
data []byte // the data to read from the Reader
finalErr error // error to return after data has been read
wantContentType string
wantContentTypeResult bool
}
for _, tc := range []testCase{
{
data: []byte{0, 0, 0, 0},
finalErr: nil,
wantContentType: "application/octet-stream",
wantContentTypeResult: true,
},
{
data: []byte(""),
finalErr: nil,
wantContentType: "text/plain; charset=utf-8",
wantContentTypeResult: true,
},
{
data: []byte(""),
finalErr: io.ErrUnexpectedEOF,
wantContentType: "text/plain; charset=utf-8",
wantContentTypeResult: false,
},
{
data: []byte("abc"),
finalErr: nil,
wantContentType: "text/plain; charset=utf-8",
wantContentTypeResult: true,
},
{
data: []byte("abc"),
finalErr: io.ErrUnexpectedEOF,
wantContentType: "text/plain; charset=utf-8",
wantContentTypeResult: false,
},
// The following examples contain more bytes than are buffered for sniffing.
{
data: bytes.Repeat([]byte("a"), 513),
finalErr: nil,
wantContentType: "text/plain; charset=utf-8",
wantContentTypeResult: true,
},
{
data: bytes.Repeat([]byte("a"), 513),
finalErr: io.ErrUnexpectedEOF,
wantContentType: "text/plain; charset=utf-8",
wantContentTypeResult: true, // true because error is after first 512 bytes.
},
} {
er := &errReader{buf: tc.data, err: tc.finalErr}
sct := newContentSniffer(er)
// Even if was an error during the first 512 bytes, we should still be able to read those bytes.
buf, err := ioutil.ReadAll(sct)
if !reflect.DeepEqual(buf, tc.data) {
t.Fatalf("Failed reading buffer: got: %q; want:%q", buf, tc.data)
}
if err != tc.finalErr {
t.Fatalf("Reading buffer error: got: %v; want: %v", err, tc.finalErr)
}
ct, ok := sct.ContentType()
if ok != tc.wantContentTypeResult {
t.Fatalf("Content type result got: %v; want: %v", ok, tc.wantContentTypeResult)
}
if ok && ct != tc.wantContentType {
t.Fatalf("Content type got: %q; want: %q", ct, tc.wantContentType)
}
}
}
type staticContentTyper struct {
io.Reader
}
func (sct staticContentTyper) ContentType() string {
return "static content type"
}
func TestDetermineContentType(t *testing.T) {
data := []byte("abc")
rdr := func() io.Reader {
return bytes.NewBuffer(data)
}
type testCase struct {
r io.Reader
explicitConentType string
wantContentType string
}
for _, tc := range []testCase{
{
r: rdr(),
wantContentType: "text/plain; charset=utf-8",
},
{
r: staticContentTyper{rdr()},
wantContentType: "static content type",
},
{
r: staticContentTyper{rdr()},
explicitConentType: "explicit",
wantContentType: "explicit",
},
} {
r, ctype := DetermineContentType(tc.r, tc.explicitConentType)
got, err := ioutil.ReadAll(r)
if err != nil {
t.Fatalf("Failed reading buffer: %v", err)
}
if !reflect.DeepEqual(got, data) {
t.Fatalf("Failed reading buffer: got: %q; want:%q", got, data)
}
if ctype != tc.wantContentType {
t.Fatalf("Content type got: %q; want: %q", ctype, tc.wantContentType)
}
}
}
+281
View File
@@ -0,0 +1,281 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gensupport
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"golang.org/x/net/context"
)
type unexpectedReader struct{}
func (unexpectedReader) Read([]byte) (int, error) {
return 0, fmt.Errorf("unexpected read in test")
}
// event is an expected request/response pair
type event struct {
// the byte range header that should be present in a request.
byteRange string
// the http status code to send in response.
responseStatus int
}
// interruptibleTransport is configured with a canned set of requests/responses.
// It records the incoming data, unless the corresponding event is configured to return
// http.StatusServiceUnavailable.
type interruptibleTransport struct {
events []event
buf []byte
bodies bodyTracker
}
// bodyTracker keeps track of response bodies that have not been closed.
type bodyTracker map[io.ReadCloser]struct{}
func (bt bodyTracker) Add(body io.ReadCloser) {
bt[body] = struct{}{}
}
func (bt bodyTracker) Close(body io.ReadCloser) {
delete(bt, body)
}
type trackingCloser struct {
io.Reader
tracker bodyTracker
}
func (tc *trackingCloser) Close() error {
tc.tracker.Close(tc)
return nil
}
func (tc *trackingCloser) Open() {
tc.tracker.Add(tc)
}
func (t *interruptibleTransport) RoundTrip(req *http.Request) (*http.Response, error) {
ev := t.events[0]
t.events = t.events[1:]
if got, want := req.Header.Get("Content-Range"), ev.byteRange; got != want {
return nil, fmt.Errorf("byte range: got %s; want %s", got, want)
}
if ev.responseStatus != http.StatusServiceUnavailable {
buf, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, fmt.Errorf("error reading from request data: %v", err)
}
t.buf = append(t.buf, buf...)
}
tc := &trackingCloser{unexpectedReader{}, t.bodies}
tc.Open()
h := http.Header{}
status := ev.responseStatus
// Support "X-GUploader-No-308" like Google:
if status == 308 && req.Header.Get("X-GUploader-No-308") == "yes" {
status = 200
h.Set("X-Http-Status-Code-Override", "308")
}
res := &http.Response{
StatusCode: status,
Header: h,
Body: tc,
}
return res, nil
}
// progressRecorder records updates, and calls f for every invocation of ProgressUpdate.
type progressRecorder struct {
updates []int64
f func()
}
func (pr *progressRecorder) ProgressUpdate(current int64) {
pr.updates = append(pr.updates, current)
if pr.f != nil {
pr.f()
}
}
func TestInterruptedTransferChunks(t *testing.T) {
type testCase struct {
data string
chunkSize int
events []event
wantProgress []int64
}
for _, tc := range []testCase{
{
data: strings.Repeat("a", 300),
chunkSize: 90,
events: []event{
{"bytes 0-89/*", http.StatusServiceUnavailable},
{"bytes 0-89/*", 308},
{"bytes 90-179/*", 308},
{"bytes 180-269/*", http.StatusServiceUnavailable},
{"bytes 180-269/*", 308},
{"bytes 270-299/300", 200},
},
wantProgress: []int64{90, 180, 270, 300},
},
{
data: strings.Repeat("a", 20),
chunkSize: 10,
events: []event{
{"bytes 0-9/*", http.StatusServiceUnavailable},
{"bytes 0-9/*", 308},
{"bytes 10-19/*", http.StatusServiceUnavailable},
{"bytes 10-19/*", 308},
// 0 byte final request demands a byte range with leading asterix.
{"bytes */20", http.StatusServiceUnavailable},
{"bytes */20", 200},
},
wantProgress: []int64{10, 20},
},
} {
media := strings.NewReader(tc.data)
tr := &interruptibleTransport{
buf: make([]byte, 0, len(tc.data)),
events: tc.events,
bodies: bodyTracker{},
}
pr := progressRecorder{}
rx := &ResumableUpload{
Client: &http.Client{Transport: tr},
Media: NewMediaBuffer(media, tc.chunkSize),
MediaType: "text/plain",
Callback: pr.ProgressUpdate,
Backoff: NoPauseStrategy,
}
res, err := rx.Upload(context.Background())
if err == nil {
res.Body.Close()
}
if err != nil || res == nil || res.StatusCode != http.StatusOK {
if res == nil {
t.Errorf("Upload not successful, res=nil: %v", err)
} else {
t.Errorf("Upload not successful, statusCode=%v: %v", res.StatusCode, err)
}
}
if !reflect.DeepEqual(tr.buf, []byte(tc.data)) {
t.Errorf("transferred contents:\ngot %s\nwant %s", tr.buf, tc.data)
}
if !reflect.DeepEqual(pr.updates, tc.wantProgress) {
t.Errorf("progress updates: got %v, want %v", pr.updates, tc.wantProgress)
}
if len(tr.events) > 0 {
t.Errorf("did not observe all expected events. leftover events: %v", tr.events)
}
if len(tr.bodies) > 0 {
t.Errorf("unclosed request bodies: %v", tr.bodies)
}
}
}
func TestCancelUploadFast(t *testing.T) {
const (
chunkSize = 90
mediaSize = 300
)
media := strings.NewReader(strings.Repeat("a", mediaSize))
tr := &interruptibleTransport{
buf: make([]byte, 0, mediaSize),
}
pr := progressRecorder{}
rx := &ResumableUpload{
Client: &http.Client{Transport: tr},
Media: NewMediaBuffer(media, chunkSize),
MediaType: "text/plain",
Callback: pr.ProgressUpdate,
Backoff: NoPauseStrategy,
}
ctx, cancelFunc := context.WithCancel(context.Background())
cancelFunc() // stop the upload that hasn't started yet
res, err := rx.Upload(ctx)
if err != context.Canceled {
t.Errorf("Upload err: got: %v; want: context cancelled", err)
}
if res != nil {
t.Errorf("Upload result: got: %v; want: nil", res)
}
if pr.updates != nil {
t.Errorf("progress updates: got %v; want: nil", pr.updates)
}
}
func TestCancelUpload(t *testing.T) {
const (
chunkSize = 90
mediaSize = 300
)
media := strings.NewReader(strings.Repeat("a", mediaSize))
tr := &interruptibleTransport{
buf: make([]byte, 0, mediaSize),
events: []event{
{"bytes 0-89/*", http.StatusServiceUnavailable},
{"bytes 0-89/*", 308},
{"bytes 90-179/*", 308},
{"bytes 180-269/*", 308}, // Upload should be cancelled before this event.
},
bodies: bodyTracker{},
}
ctx, cancelFunc := context.WithCancel(context.Background())
numUpdates := 0
pr := progressRecorder{f: func() {
numUpdates++
if numUpdates >= 2 {
cancelFunc()
}
}}
rx := &ResumableUpload{
Client: &http.Client{Transport: tr},
Media: NewMediaBuffer(media, chunkSize),
MediaType: "text/plain",
Callback: pr.ProgressUpdate,
Backoff: NoPauseStrategy,
}
res, err := rx.Upload(ctx)
if err != context.Canceled {
t.Errorf("Upload err: got: %v; want: context cancelled", err)
}
if res != nil {
t.Errorf("Upload result: got: %v; want: nil", res)
}
if got, want := tr.buf, []byte(strings.Repeat("a", chunkSize*2)); !reflect.DeepEqual(got, want) {
t.Errorf("transferred contents:\ngot %s\nwant %s", got, want)
}
if got, want := pr.updates, []int64{chunkSize, chunkSize * 2}; !reflect.DeepEqual(got, want) {
t.Errorf("progress updates: got %v; want: %v", got, want)
}
if len(tr.bodies) > 0 {
t.Errorf("unclosed request bodies: %v", tr.bodies)
}
}
+176
View File
@@ -0,0 +1,176 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gensupport
import (
"errors"
"io"
"net"
"net/http"
"testing"
"time"
"golang.org/x/net/context"
)
func TestRetry(t *testing.T) {
testCases := []struct {
desc string
respStatus []int // HTTP status codes returned (length indicates number of calls we expect).
maxRetry int // Max number of calls allowed by the BackoffStrategy.
wantStatus int // StatusCode of returned response.
}{
{
desc: "First call successful",
respStatus: []int{200},
maxRetry: 3,
wantStatus: 200,
},
{
desc: "Retry before success",
respStatus: []int{500, 500, 500, 200},
maxRetry: 3,
wantStatus: 200,
},
{
desc: "Backoff strategy abandons after 3 retries",
respStatus: []int{500, 500, 500, 500},
maxRetry: 3,
wantStatus: 500,
},
{
desc: "Backoff strategy abandons after 2 retries",
respStatus: []int{500, 500, 500},
maxRetry: 2,
wantStatus: 500,
},
}
for _, tt := range testCases {
// Function consumes tt.respStatus
f := func() (*http.Response, error) {
if len(tt.respStatus) == 0 {
return nil, errors.New("too many requests to function")
}
resp := &http.Response{StatusCode: tt.respStatus[0]}
tt.respStatus = tt.respStatus[1:]
return resp, nil
}
backoff := &LimitRetryStrategy{
Max: tt.maxRetry,
Strategy: NoPauseStrategy,
}
resp, err := Retry(nil, f, backoff)
if err != nil {
t.Errorf("%s: Retry returned err %v", tt.desc, err)
}
if got := resp.StatusCode; got != tt.wantStatus {
t.Errorf("%s: Retry returned response with StatusCode=%d; want %d", got, tt.wantStatus)
}
if len(tt.respStatus) != 0 {
t.Errorf("%s: f was not called enough; status codes remaining: %v", tt.desc, tt.respStatus)
}
}
}
type checkCloseReader struct {
closed bool
}
func (c *checkCloseReader) Read(p []byte) (n int, err error) { return 0, io.EOF }
func (c *checkCloseReader) Close() error {
c.closed = true
return nil
}
func TestRetryClosesBody(t *testing.T) {
var i int
responses := []*http.Response{
{StatusCode: 500, Body: &checkCloseReader{}},
{StatusCode: 500, Body: &checkCloseReader{}},
{StatusCode: 200, Body: &checkCloseReader{}},
}
f := func() (*http.Response, error) {
resp := responses[i]
i++
return resp, nil
}
resp, err := Retry(nil, f, NoPauseStrategy)
if err != nil {
t.Fatalf("Retry returned error: %v", err)
}
if resp != responses[2] {
t.Errorf("Retry returned %v; want %v", resp, responses[2])
}
for i, resp := range responses {
want := i != 2 // Only the last response should not be closed.
got := resp.Body.(*checkCloseReader).closed
if got != want {
t.Errorf("response[%d].Body closed = %t, want %t", got, want)
}
}
}
func RetryReturnsOnContextCancel(t *testing.T) {
f := func() (*http.Response, error) {
return nil, io.ErrUnexpectedEOF
}
backoff := UniformPauseStrategy(time.Hour)
ctx, cancel := context.WithCancel(context.Background())
errc := make(chan error, 1)
go func() {
_, err := Retry(ctx, f, backoff)
errc <- err
}()
cancel()
select {
case err := <-errc:
if err != ctx.Err() {
t.Errorf("Retry returned err: %v, want %v", err, ctx.Err())
}
case <-time.After(5 * time.Second):
t.Errorf("Timed out waiting for Retry to return")
}
}
func TestShouldRetry(t *testing.T) {
testCases := []struct {
status int
err error
want bool
}{
{status: 200, want: false},
{status: 308, want: false},
{status: 403, want: false},
{status: 429, want: true},
{status: 500, want: true},
{status: 503, want: true},
{status: 600, want: false},
{err: io.EOF, want: false},
{err: errors.New("random badness"), want: false},
{err: io.ErrUnexpectedEOF, want: true},
{err: &net.AddrError{}, want: false}, // Not temporary.
{err: &net.DNSError{IsTimeout: true}, want: true}, // Temporary.
}
for _, tt := range testCases {
if got := shouldRetry(tt.status, tt.err); got != tt.want {
t.Errorf("shouldRetry(%d, %v) = %t; want %t", tt.status, tt.err, got, tt.want)
}
}
}
+57
View File
@@ -0,0 +1,57 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gensupport
import (
"io"
"time"
)
// errReader reads out of a buffer until it is empty, then returns the specified error.
type errReader struct {
buf []byte
err error
}
func (er *errReader) Read(p []byte) (int, error) {
if len(er.buf) == 0 {
if er.err == nil {
return 0, io.EOF
}
return 0, er.err
}
n := copy(p, er.buf)
er.buf = er.buf[n:]
return n, nil
}
// UniformPauseStrategy implements BackoffStrategy with uniform pause.
type UniformPauseStrategy time.Duration
func (p UniformPauseStrategy) Pause() (time.Duration, bool) { return time.Duration(p), true }
func (p UniformPauseStrategy) Reset() {}
// NoPauseStrategy implements BackoffStrategy with infinite 0-length pauses.
const NoPauseStrategy = UniformPauseStrategy(0)
// LimitRetryStrategy wraps a BackoffStrategy but limits the number of retries.
type LimitRetryStrategy struct {
Max int
Strategy BackoffStrategy
n int
}
func (l *LimitRetryStrategy) Pause() (time.Duration, bool) {
l.n++
if l.n > l.Max {
return 0, false
}
return l.Strategy.Pause()
}
func (l *LimitRetryStrategy) Reset() {
l.n = 0
l.Strategy.Reset()
}