mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
* add jaeger support, link hot container & req span * adds jaeger support now with FN_JAEGER_URL, there's a simple tutorial in the operating/metrics.md file now and it's pretty easy to get up and running. * links a hot request span to a hot container span. when we change this to sample at a lower ratio we'll need to finagle the hot container span to always sample or something, otherwise we'll hide that info. at least, since we're sampling at 100% for now if this is flipped on, can see freeze/unfreeze etc. if they hit. this is useful for debugging. note that zipkin's exporter does not follow the link at all, hence jaeger... and they're backed by the Cloud Empire now (CNCF) so we'll probably use it anyway. * vendor: add thrift for jaeger
408 lines
12 KiB
Go
408 lines
12 KiB
Go
// 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"
|
|
"crypto/rand"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"google.golang.org/api/googleapi"
|
|
)
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNewInfoFromMedia(t *testing.T) {
|
|
const textType = "text/plain; charset=utf-8"
|
|
for _, test := range []struct {
|
|
desc string
|
|
r io.Reader
|
|
opts []googleapi.MediaOption
|
|
wantType string
|
|
wantMedia, wantBuffer, wantSingleChunk bool
|
|
}{
|
|
{
|
|
desc: "an empty reader results in a MediaBuffer with a single, empty chunk",
|
|
r: new(bytes.Buffer),
|
|
opts: nil,
|
|
wantType: textType,
|
|
wantBuffer: true,
|
|
wantSingleChunk: true,
|
|
},
|
|
{
|
|
desc: "ContentType is observed",
|
|
r: new(bytes.Buffer),
|
|
opts: []googleapi.MediaOption{googleapi.ContentType("xyz")},
|
|
wantType: "xyz",
|
|
wantBuffer: true,
|
|
wantSingleChunk: true,
|
|
},
|
|
{
|
|
desc: "chunk size of zero: don't use a MediaBuffer; upload as a single chunk",
|
|
r: strings.NewReader("12345"),
|
|
opts: []googleapi.MediaOption{googleapi.ChunkSize(0)},
|
|
wantType: textType,
|
|
wantMedia: true,
|
|
wantSingleChunk: true,
|
|
},
|
|
{
|
|
desc: "chunk size > data size: MediaBuffer with single chunk",
|
|
r: strings.NewReader("12345"),
|
|
opts: []googleapi.MediaOption{googleapi.ChunkSize(100)},
|
|
wantType: textType,
|
|
wantBuffer: true,
|
|
wantSingleChunk: true,
|
|
},
|
|
{
|
|
desc: "chunk size == data size: MediaBuffer with single chunk",
|
|
r: &nullReader{googleapi.MinUploadChunkSize},
|
|
opts: []googleapi.MediaOption{googleapi.ChunkSize(1)},
|
|
wantType: "application/octet-stream",
|
|
wantBuffer: true,
|
|
wantSingleChunk: true,
|
|
},
|
|
{
|
|
desc: "chunk size < data size: MediaBuffer, not single chunk",
|
|
// Note that ChunkSize = 1 is rounded up to googleapi.MinUploadChunkSize.
|
|
r: &nullReader{2 * googleapi.MinUploadChunkSize},
|
|
opts: []googleapi.MediaOption{googleapi.ChunkSize(1)},
|
|
wantType: "application/octet-stream",
|
|
wantBuffer: true,
|
|
wantSingleChunk: false,
|
|
},
|
|
} {
|
|
|
|
mi := NewInfoFromMedia(test.r, test.opts)
|
|
if got, want := mi.mType, test.wantType; got != want {
|
|
t.Errorf("%s: type: got %q, want %q", test.desc, got, want)
|
|
}
|
|
if got, want := (mi.media != nil), test.wantMedia; got != want {
|
|
t.Errorf("%s: media non-nil: got %t, want %t", test.desc, got, want)
|
|
}
|
|
if got, want := (mi.buffer != nil), test.wantBuffer; got != want {
|
|
t.Errorf("%s: buffer non-nil: got %t, want %t", test.desc, got, want)
|
|
}
|
|
if got, want := mi.singleChunk, test.wantSingleChunk; got != want {
|
|
t.Errorf("%s: singleChunk: got %t, want %t", test.desc, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUploadRequest(t *testing.T) {
|
|
for _, test := range []struct {
|
|
desc string
|
|
r io.Reader
|
|
chunkSize int
|
|
wantContentType string
|
|
wantUploadType string
|
|
}{
|
|
{
|
|
desc: "chunk size of zero: don't use a MediaBuffer; upload as a single chunk",
|
|
r: strings.NewReader("12345"),
|
|
chunkSize: 0,
|
|
wantContentType: "multipart/related;",
|
|
},
|
|
{
|
|
desc: "chunk size > data size: MediaBuffer with single chunk",
|
|
r: strings.NewReader("12345"),
|
|
chunkSize: 100,
|
|
wantContentType: "multipart/related;",
|
|
},
|
|
{
|
|
desc: "chunk size == data size: MediaBuffer with single chunk",
|
|
r: &nullReader{googleapi.MinUploadChunkSize},
|
|
chunkSize: 1,
|
|
wantContentType: "multipart/related;",
|
|
},
|
|
{
|
|
desc: "chunk size < data size: MediaBuffer, not single chunk",
|
|
// Note that ChunkSize = 1 is rounded up to googleapi.MinUploadChunkSize.
|
|
r: &nullReader{2 * googleapi.MinUploadChunkSize},
|
|
chunkSize: 1,
|
|
wantUploadType: "application/octet-stream",
|
|
},
|
|
} {
|
|
mi := NewInfoFromMedia(test.r, []googleapi.MediaOption{googleapi.ChunkSize(test.chunkSize)})
|
|
h := http.Header{}
|
|
mi.UploadRequest(h, new(bytes.Buffer))
|
|
if got, want := h.Get("Content-Type"), test.wantContentType; !strings.HasPrefix(got, want) {
|
|
t.Errorf("%s: Content-Type: got %q, want prefix %q", test.desc, got, want)
|
|
}
|
|
if got, want := h.Get("X-Upload-Content-Type"), test.wantUploadType; got != want {
|
|
t.Errorf("%s: X-Upload-Content-Type: got %q, want %q", test.desc, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUploadRequestGetBody(t *testing.T) {
|
|
// Test that a single chunk results in a getBody function that is non-nil, and
|
|
// that produces the same content as the original body.
|
|
|
|
// Mock out rand.Reader so we use the same multipart boundary every time.
|
|
rr := rand.Reader
|
|
rand.Reader = &nullReader{1000}
|
|
defer func() {
|
|
rand.Reader = rr
|
|
}()
|
|
|
|
for _, test := range []struct {
|
|
desc string
|
|
r io.Reader
|
|
chunkSize int
|
|
wantGetBody bool
|
|
wantContentType string
|
|
wantUploadType string
|
|
}{
|
|
{
|
|
desc: "chunk size of zero: no getBody",
|
|
r: &nullReader{10},
|
|
chunkSize: 0,
|
|
wantGetBody: false,
|
|
},
|
|
{
|
|
desc: "chunk size == data size: 1 chunk, getBody",
|
|
r: &nullReader{googleapi.MinUploadChunkSize},
|
|
chunkSize: 1,
|
|
wantGetBody: true,
|
|
},
|
|
{
|
|
desc: "chunk size < data size: MediaBuffer, >1 chunk, no getBody",
|
|
// No getBody here, because the initial request contains no media data
|
|
// Note that ChunkSize = 1 is rounded up to googleapi.MinUploadChunkSize.
|
|
r: &nullReader{2 * googleapi.MinUploadChunkSize},
|
|
chunkSize: 1,
|
|
wantGetBody: false,
|
|
},
|
|
} {
|
|
mi := NewInfoFromMedia(test.r, []googleapi.MediaOption{googleapi.ChunkSize(test.chunkSize)})
|
|
r, getBody, _ := mi.UploadRequest(http.Header{}, bytes.NewBuffer([]byte("body")))
|
|
if got, want := (getBody != nil), test.wantGetBody; got != want {
|
|
t.Errorf("%s: getBody: got %t, want %t", test.desc, got, want)
|
|
continue
|
|
}
|
|
if getBody == nil {
|
|
continue
|
|
}
|
|
want, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i := 0; i < 3; i++ {
|
|
rc, err := getBody()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := ioutil.ReadAll(rc)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(got, want) {
|
|
t.Errorf("%s, %d:\ngot:\n%s\nwant:\n%s", test.desc, i, string(got), string(want))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResumableUpload(t *testing.T) {
|
|
for _, test := range []struct {
|
|
desc string
|
|
r io.Reader
|
|
chunkSize int
|
|
wantUploadType string
|
|
wantResumableUpload bool
|
|
}{
|
|
{
|
|
desc: "chunk size of zero: don't use a MediaBuffer; upload as a single chunk",
|
|
r: strings.NewReader("12345"),
|
|
chunkSize: 0,
|
|
wantUploadType: "multipart",
|
|
wantResumableUpload: false,
|
|
},
|
|
{
|
|
desc: "chunk size > data size: MediaBuffer with single chunk",
|
|
r: strings.NewReader("12345"),
|
|
chunkSize: 100,
|
|
wantUploadType: "multipart",
|
|
wantResumableUpload: false,
|
|
},
|
|
{
|
|
desc: "chunk size == data size: MediaBuffer with single chunk",
|
|
// (Because nullReader returns EOF with the last bytes.)
|
|
r: &nullReader{googleapi.MinUploadChunkSize},
|
|
chunkSize: googleapi.MinUploadChunkSize,
|
|
wantUploadType: "multipart",
|
|
wantResumableUpload: false,
|
|
},
|
|
{
|
|
desc: "chunk size < data size: MediaBuffer, not single chunk",
|
|
// Note that ChunkSize = 1 is rounded up to googleapi.MinUploadChunkSize.
|
|
r: &nullReader{2 * googleapi.MinUploadChunkSize},
|
|
chunkSize: 1,
|
|
wantUploadType: "resumable",
|
|
wantResumableUpload: true,
|
|
},
|
|
} {
|
|
mi := NewInfoFromMedia(test.r, []googleapi.MediaOption{googleapi.ChunkSize(test.chunkSize)})
|
|
if got, want := mi.UploadType(), test.wantUploadType; got != want {
|
|
t.Errorf("%s: upload type: got %q, want %q", test.desc, got, want)
|
|
}
|
|
if got, want := mi.ResumableUpload("") != nil, test.wantResumableUpload; got != want {
|
|
t.Errorf("%s: resumable upload non-nil: got %t, want %t", test.desc, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A nullReader simulates reading a fixed number of bytes.
|
|
type nullReader struct {
|
|
remain int
|
|
}
|
|
|
|
// Read doesn't touch buf, but it does reduce the amount of bytes remaining
|
|
// by len(buf).
|
|
func (r *nullReader) Read(buf []byte) (int, error) {
|
|
n := len(buf)
|
|
if r.remain < n {
|
|
n = r.remain
|
|
}
|
|
r.remain -= n
|
|
var err error
|
|
if r.remain == 0 {
|
|
err = io.EOF
|
|
}
|
|
return n, err
|
|
}
|