func TestResMeta(t *testing.T) {
	tests := []struct {
		build func(*CallResMetaBuilder) *CallResMetaBuilder

		wantHeaders yarpc.Headers
	}{
		{
			build: func(r *CallResMetaBuilder) *CallResMetaBuilder {
				return r
			},
			wantHeaders: yarpc.NewHeaders(),
		},
		{
			build: func(r *CallResMetaBuilder) *CallResMetaBuilder {
				return r.Headers(yarpc.NewHeaders().With("foo", "bar"))
			},
			wantHeaders: yarpc.NewHeaders().With("foo", "bar"),
		},
	}

	for _, tt := range tests {
		reqMeta := tt.build(NewCallResMetaBuilder()).Build()
		assert.Equal(t, tt.wantHeaders, reqMeta.Headers())
	}
}
Beispiel #2
0
// RemoveVariableHeaderKeys removes any headers that might have been added by tracing
func RemoveVariableHeaderKeys(headers yarpc.Headers) yarpc.Headers {
	headers.Del("$tracing$uber-trace-id")
	if headers.Len() == 0 {
		return yarpc.NewHeaders()
	}
	return headers
}
Beispiel #3
0
// remoteTimeout tests if a yarpc client returns a remote timeout error behind
// the TimeoutError interface when a remote tchannel handler returns a handler
// timeout.
func remoteTimeout(t crossdock.T, dispatcher yarpc.Dispatcher) {
	assert := crossdock.Assert(t)

	headers := yarpc.NewHeaders()
	token := random.Bytes(5)

	_, _, err := rawCall(dispatcher, headers, "handlertimeout/raw", token)
	if skipOnConnRefused(t, err) {
		return
	}
	if !assert.Error(err, "expected an error") {
		return
	}

	if transport.IsBadRequestError(err) {
		t.Skipf("handlertimeout/raw procedure not implemented: %v", err)
		return
	}

	assert.True(transport.IsTimeoutError(err), "returns a TimeoutError: %T", err)

	form := strings.HasPrefix(err.Error(),
		`Timeout: call to procedure "handlertimeout/raw" of service "service" from caller "caller" timed out after`)
	assert.True(form, "must be a remote handler timeout: %q", err.Error())
}
Beispiel #4
0
func TestReqMeta(t *testing.T) {
	tests := []struct {
		build func(*ReqMetaBuilder) *ReqMetaBuilder

		wantEncoding  transport.Encoding
		wantHeaders   yarpc.Headers
		wantCaller    string
		wantProcedure string
		wantService   string
	}{
		{
			build: func(r *ReqMetaBuilder) *ReqMetaBuilder {
				return r
			},
			wantEncoding:  "",
			wantHeaders:   yarpc.NewHeaders(),
			wantCaller:    "",
			wantProcedure: "",
			wantService:   "",
		},
		{
			build: func(r *ReqMetaBuilder) *ReqMetaBuilder {
				return r.
					Encoding(transport.Encoding("myencoding")).
					Headers(yarpc.NewHeaders().With("foo", "bar")).
					Caller("caller").
					Service("service").
					Procedure("procedure")
			},
			wantEncoding:  "myencoding",
			wantHeaders:   yarpc.NewHeaders().With("foo", "bar"),
			wantCaller:    "caller",
			wantService:   "service",
			wantProcedure: "procedure",
		},
	}

	for _, tt := range tests {
		reqMeta := tt.build(NewReqMetaBuilder()).Build()
		assert.Equal(t, tt.wantEncoding, reqMeta.Encoding())
		assert.Equal(t, tt.wantHeaders, reqMeta.Headers())
		assert.Equal(t, tt.wantCaller, reqMeta.Caller())
		assert.Equal(t, tt.wantProcedure, reqMeta.Procedure())
		assert.Equal(t, tt.wantService, reqMeta.Service())
	}
}
Beispiel #5
0
func call(client helloclient.Interface, message string) (*echo.EchoResponse, yarpc.Headers) {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()

	resBody, resMeta, err := client.Echo(
		ctx,
		yarpc.NewReqMeta().Headers(yarpc.NewHeaders().With("from", "self")),
		&echo.EchoRequest{Message: message, Count: 1},
	)
	if err != nil {
		log.Fatal(err)
	}

	return resBody, resMeta.Headers()
}
Beispiel #6
0
func runJSON(t crossdock.T, dispatcher yarpc.Dispatcher) {
	assert := crossdock.Assert(t)
	checks := crossdock.Checks(t)

	headers := yarpc.NewHeaders().With("hello", "json")
	token := random.String(5)

	resBody, resMeta, err := jsonCall(dispatcher, headers, token)
	if skipOnConnRefused(t, err) {
		return
	}
	if checks.NoError(err, "json: call failed") {
		assert.Equal(token, resBody, "body echoed")
		resHeaders := internal.RemoveVariableHeaderKeys(resMeta.Headers())
		assert.Equal(headers, resHeaders, "headers echoed")
	}
}
Beispiel #7
0
func hello(t crossdock.T, dispatcher yarpc.Dispatcher) {
	assert := crossdock.Assert(t)
	checks := crossdock.Checks(t)

	// TODO headers should be at yarpc, not transport
	headers := yarpc.NewHeaders().With("hello", "raw")
	token := random.Bytes(5)

	resBody, resMeta, err := rawCall(dispatcher, headers, "echo/raw", token)
	if skipOnConnRefused(t, err) {
		return
	}
	if checks.NoError(err, "raw: call failed") {
		assert.Equal(token, resBody, "body echoed")
		resHeaders := internal.RemoveVariableHeaderKeys(resMeta.Headers())
		assert.Equal(headers, resHeaders, "headers echoed")
	}
}
Beispiel #8
0
func TestHandleSuccessWithResponseHeaders(t *testing.T) {
	h := func(ctx context.Context, r yarpc.ReqMeta, _ *simpleRequest) (*simpleResponse, yarpc.ResMeta, error) {
		resMeta := yarpc.NewResMeta().Headers(yarpc.NewHeaders().With("foo", "bar"))
		return &simpleResponse{Success: true}, resMeta, nil
	}

	handler := jsonHandler{
		reader:  structReader{reflect.TypeOf(simpleRequest{})},
		handler: reflect.ValueOf(h),
	}

	resw := new(transporttest.FakeResponseWriter)
	err := handler.Handle(context.Background(), &transport.Request{
		Procedure: "simpleCall",
		Encoding:  "json",
		Body:      jsonBody(`{"name": "foo", "attributes": {"bar": 42}}`),
	}, resw)
	require.NoError(t, err)

	assert.Equal(t, transport.NewHeaders().With("foo", "bar"), resw.Headers)
}
Beispiel #9
0
func runThrift(t crossdock.T, dispatcher yarpc.Dispatcher) {
	assert := crossdock.Assert(t)
	checks := crossdock.Checks(t)

	headers := yarpc.NewHeaders().With("hello", "thrift")
	token := random.String(5)

	resBody, resMeta, err := thriftCall(dispatcher, headers, token)
	if skipOnConnRefused(t, err) {
		return
	}
	if checks.NoError(err, "thrift: call failed") {
		assert.Equal(token, resBody, "body echoed")
		resHeaders := internal.RemoveVariableHeaderKeys(resMeta.Headers())
		assert.Equal(headers, resHeaders, "headers echoed")
	}

	t.Tag("server", t.Param(params.Server))
	gauntlet.RunGauntlet(t, gauntlet.Config{
		Dispatcher: dispatcher,
		ServerName: serverName,
	})
}
Beispiel #10
0
// Run runs the headers behavior
func Run(t crossdock.T) {
	t = createHeadersT(t)

	fatals := crossdock.Fatals(t)
	assert := crossdock.Assert(t)
	checks := crossdock.Checks(t)

	dispatcher := disp.Create(t)
	fatals.NoError(dispatcher.Start(), "could not start Dispatcher")
	defer dispatcher.Stop()

	var caller headerCaller
	encoding := t.Param(params.Encoding)
	switch encoding {
	case "raw":
		caller = rawCaller{raw.New(dispatcher.Channel("yarpc-test"))}
	case "json":
		caller = jsonCaller{json.New(dispatcher.Channel("yarpc-test"))}
	case "thrift":
		caller = thriftCaller{echoclient.New(dispatcher.Channel("yarpc-test"))}
	default:
		fatals.Fail("", "unknown encoding %q", encoding)
	}

	token1 := random.String(10)
	token2 := random.String(10)

	tests := []struct {
		desc string
		give yarpc.Headers
		want yarpc.Headers
	}{
		{
			"valid headers",
			yarpc.NewHeaders().With("token1", token1).With("token2", token2),
			yarpc.NewHeaders().With("token1", token1).With("token2", token2),
		},
		{
			"non-string values",
			yarpc.NewHeaders().With("token", "42"),
			yarpc.NewHeaders().With("token", "42"),
		},
		{
			"empty strings",
			yarpc.NewHeaders().With("token", ""),
			yarpc.NewHeaders().With("token", ""),
		},
		{
			"no headers",
			yarpc.Headers{},
			yarpc.NewHeaders(),
		},
		{
			"empty map",
			yarpc.NewHeaders(),
			yarpc.NewHeaders(),
		},
		{
			"varying casing",
			yarpc.NewHeaders().With("ToKeN1", token1).With("tOkEn2", token2),
			yarpc.NewHeaders().With("token1", token1).With("token2", token2),
		},
		{
			"http header conflict",
			yarpc.NewHeaders().With("Rpc-Procedure", "does not exist"),
			yarpc.NewHeaders().With("rpc-procedure", "does not exist"),
		},
		{
			"mixed case value",
			yarpc.NewHeaders().With("token", "MIXED case Value"),
			yarpc.NewHeaders().With("token", "MIXED case Value"),
		},
	}

	for _, tt := range tests {
		got, err := caller.Call(tt.give)
		if checks.NoError(err, "%v: call failed", tt.desc) {
			gotHeaders := internal.RemoveVariableHeaderKeys(got)
			assert.Equal(tt.want, gotHeaders, "%v: returns valid headers", tt.desc)
		}
	}
}
Beispiel #11
0
func TestCall(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	ctx := context.Background()

	caller := "caller"
	service := "service"

	tests := []struct {
		procedure    string
		headers      yarpc.Headers
		body         []byte
		responseBody [][]byte

		want        []byte
		wantErr     string
		wantHeaders yarpc.Headers
	}{
		{
			procedure:    "foo",
			body:         []byte{1, 2, 3},
			responseBody: [][]byte{{4}, {5}, {6}},
			want:         []byte{4, 5, 6},
		},
		{
			procedure:    "bar",
			body:         []byte{1, 2, 3},
			responseBody: [][]byte{{4}, {5}, nil, {6}},
			wantErr:      "error set by user",
		},
		{
			procedure:    "headers",
			headers:      yarpc.NewHeaders().With("x", "y"),
			body:         []byte{},
			responseBody: [][]byte{},
			want:         []byte{},
			wantHeaders:  yarpc.NewHeaders().With("a", "b"),
		},
	}

	for _, tt := range tests {
		outbound := transporttest.NewMockUnaryOutbound(mockCtrl)
		client := New(channel.MultiOutbound(caller, service,
			transport.Outbounds{
				Unary: outbound,
			}))

		writer, responseBody := testreader.ChunkReader()
		for _, chunk := range tt.responseBody {
			writer <- chunk
		}
		close(writer)

		outbound.EXPECT().Call(gomock.Any(),
			transporttest.NewRequestMatcher(t,
				&transport.Request{
					Caller:    caller,
					Service:   service,
					Procedure: tt.procedure,
					Headers:   transport.Headers(tt.headers),
					Encoding:  Encoding,
					Body:      bytes.NewReader(tt.body),
				}),
		).Return(
			&transport.Response{
				Body:    ioutil.NopCloser(responseBody),
				Headers: transport.Headers(tt.wantHeaders),
			}, nil)

		resBody, res, err := client.Call(
			ctx,
			yarpc.NewReqMeta().Procedure(tt.procedure).Headers(tt.headers),
			tt.body)

		if tt.wantErr != "" {
			if assert.Error(t, err) {
				assert.Equal(t, err.Error(), tt.wantErr)
			}
		} else {
			if assert.NoError(t, err) {
				assert.Equal(t, tt.want, resBody)
				assert.Equal(t, tt.wantHeaders, res.Headers())
			}
		}
	}
}
Beispiel #12
0
func TestCallOneway(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	ctx := context.Background()

	caller := "caller"
	service := "service"

	tests := []struct {
		procedure string
		headers   yarpc.Headers
		body      []byte

		wantErr     string
		wantHeaders yarpc.Headers
	}{
		{
			procedure: "foo",
			body:      []byte{1, 2, 3},
		},
		{
			procedure: "headers",
			headers:   yarpc.NewHeaders().With("x", "y"),
			body:      []byte{},
		},
	}

	for _, tt := range tests {
		outbound := transporttest.NewMockOnewayOutbound(mockCtrl)
		client := New(channel.MultiOutbound(caller, service,
			transport.Outbounds{
				Oneway: outbound,
			}))

		outbound.EXPECT().CallOneway(gomock.Any(),
			transporttest.NewRequestMatcher(t,
				&transport.Request{
					Caller:    caller,
					Service:   service,
					Procedure: tt.procedure,
					Headers:   transport.Headers(tt.headers),
					Encoding:  Encoding,
					Body:      bytes.NewReader(tt.body),
				}),
		).Return(&successAck{}, nil)

		ack, err := client.CallOneway(
			ctx,
			yarpc.NewReqMeta().Procedure(tt.procedure).Headers(tt.headers),
			tt.body)

		if tt.wantErr != "" {
			if assert.Error(t, err) {
				assert.Equal(t, err.Error(), tt.wantErr)
			}
		} else {
			assert.Equal(t, "success", ack.String())
		}
	}
}
Beispiel #13
0
func TestRawHandler(t *testing.T) {
	// handler to use for test cases where the handler should not be called
	handlerNotCalled :=
		func(ctx context.Context, reqMeta yarpc.ReqMeta, body []byte) ([]byte, yarpc.ResMeta, error) {
			t.Errorf("unexpected call handle(%v, %v)", reqMeta, body)
			return nil, nil, fmt.Errorf("unexpected call handle(%v, %v)", reqMeta, body)
		}

	tests := []struct {
		procedure  string
		headers    transport.Headers
		bodyChunks [][]byte
		handler    UnaryHandler

		wantErr     string
		wantHeaders transport.Headers
		wantBody    []byte
	}{
		{
			procedure: "foo",
			bodyChunks: [][]byte{
				{1, 2, 3},
				{4, 5, 6},
			},
			handler: func(ctx context.Context, reqMeta yarpc.ReqMeta, body []byte) ([]byte, yarpc.ResMeta, error) {
				assert.Equal(t, "foo", reqMeta.Procedure())
				assert.Equal(t, []byte{1, 2, 3, 4, 5, 6}, body)
				return []byte("hello"), nil, nil
			},
			wantBody: []byte("hello"),
		},
		{
			procedure: "bar",
			bodyChunks: [][]byte{
				{1, 2, 3},
				nil, // triggers a read error
				{4, 5, 6},
			},
			handler: handlerNotCalled,
			wantErr: "error set by user",
			// TODO consistent error messages between languages
		},
		{
			procedure:  "baz",
			bodyChunks: [][]byte{},
			handler: func(ctx context.Context, reqMeta yarpc.ReqMeta, body []byte) ([]byte, yarpc.ResMeta, error) {
				assert.Equal(t, []byte{}, body)
				return nil, nil, fmt.Errorf("great sadness")
			},
			wantErr: "great sadness",
		},
		{
			procedure:  "responseHeaders",
			bodyChunks: [][]byte{},
			handler: func(ctx context.Context, reqMeta yarpc.ReqMeta, body []byte) ([]byte, yarpc.ResMeta, error) {
				resMeta := yarpc.NewResMeta().Headers(yarpc.NewHeaders().With("hello", "world"))
				return []byte{}, resMeta, nil
			},
			wantHeaders: transport.NewHeaders().With("hello", "world"),
		},
	}

	for _, tt := range tests {
		handler := rawUnaryHandler{tt.handler}
		resw := new(transporttest.FakeResponseWriter)

		writer, chunkReader := testreader.ChunkReader()
		for _, chunk := range tt.bodyChunks {
			writer <- chunk
		}
		close(writer)

		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()

		err := handler.Handle(ctx, &transport.Request{
			Procedure: tt.procedure,
			Headers:   tt.headers,
			Encoding:  "raw",
			Body:      chunkReader,
		}, resw)

		if tt.wantErr != "" {
			if assert.Error(t, err) {
				assert.Equal(t, err.Error(), tt.wantErr)
			}
		} else {
			if assert.NoError(t, err) {
				assert.Equal(t, tt.wantHeaders, resw.Headers)
				assert.Equal(t, tt.wantBody, resw.Body.Bytes(),
					"body does not match for %s", tt.procedure)
			}
		}
	}
}
Beispiel #14
0
func TestCall(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	ctx := context.Background()

	caller := "caller"
	service := "service"

	tests := []struct {
		procedure       string
		headers         yarpc.Headers
		body            interface{}
		encodedRequest  string
		encodedResponse string

		// whether the outbound receives the request
		noCall bool

		// Either want, or wantType and wantErr must be set.
		want        interface{} // expected response body
		wantHeaders yarpc.Headers
		wantType    reflect.Type // type of response body
		wantErr     string       // error message
	}{
		{
			procedure:       "foo",
			body:            []string{"foo", "bar"},
			encodedRequest:  `["foo","bar"]`,
			encodedResponse: `{"success": true}`,
			want:            map[string]interface{}{"success": true},
		},
		{
			procedure:       "bar",
			body:            []int{1, 2, 3},
			encodedRequest:  `[1,2,3]`,
			encodedResponse: `invalid JSON`,
			wantType:        _typeOfMapInterface,
			wantErr:         `failed to decode "json" response body for procedure "bar" of service "service"`,
		},
		{
			procedure: "baz",
			body:      func() {}, // funcs cannot be json.Marshal'ed
			noCall:    true,
			wantType:  _typeOfMapInterface,
			wantErr:   `failed to encode "json" request body for procedure "baz" of service "service"`,
		},
		{
			procedure:       "requestHeaders",
			headers:         yarpc.NewHeaders().With("user-id", "42"),
			body:            map[string]interface{}{},
			encodedRequest:  "{}",
			encodedResponse: "{}",
			want:            map[string]interface{}{},
			wantHeaders:     yarpc.NewHeaders().With("success", "true"),
		},
	}

	for _, tt := range tests {
		outbound := transporttest.NewMockUnaryOutbound(mockCtrl)
		client := New(channel.MultiOutbound(caller, service,
			transport.Outbounds{
				Unary: outbound,
			}))

		if !tt.noCall {
			outbound.EXPECT().Call(gomock.Any(),
				transporttest.NewRequestMatcher(t,
					&transport.Request{
						Caller:    caller,
						Service:   service,
						Procedure: tt.procedure,
						Encoding:  Encoding,
						Headers:   transport.Headers(tt.headers),
						Body:      bytes.NewReader([]byte(tt.encodedRequest)),
					}),
			).Return(
				&transport.Response{
					Body: ioutil.NopCloser(
						bytes.NewReader([]byte(tt.encodedResponse))),
					Headers: transport.Headers(tt.wantHeaders),
				}, nil)
		}

		var wantType reflect.Type
		if tt.want != nil {
			wantType = reflect.TypeOf(tt.want)
		} else {
			require.NotNil(t, tt.wantType, "wantType is required if want is nil")
			wantType = tt.wantType
		}
		resBody := reflect.Zero(wantType).Interface()

		res, err := client.Call(
			ctx,
			yarpc.NewReqMeta().Procedure(tt.procedure).Headers(tt.headers),
			tt.body,
			&resBody,
		)

		if tt.wantErr != "" {
			if assert.Error(t, err) {
				assert.Contains(t, err.Error(), tt.wantErr)
			}
		} else {
			if assert.NoError(t, err) {
				assert.Equal(t, tt.wantHeaders, res.Headers())
				assert.Equal(t, tt.want, resBody)
			}
		}
	}
}
Beispiel #15
0
func TestCallOneway(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	ctx := context.Background()

	caller := "caller"
	service := "service"

	tests := []struct {
		procedure      string
		headers        yarpc.Headers
		body           interface{}
		encodedRequest string

		// whether the outbound receives the request
		noCall bool

		wantErr string // error message
	}{
		{
			procedure:      "foo",
			body:           []string{"foo", "bar"},
			encodedRequest: `["foo","bar"]` + "\n",
		},
		{
			procedure: "baz",
			body:      func() {}, // funcs cannot be json.Marshal'ed
			noCall:    true,
			wantErr:   `failed to encode "json" request body for procedure "baz" of service "service"`,
		},
		{
			procedure:      "requestHeaders",
			headers:        yarpc.NewHeaders().With("user-id", "42"),
			body:           map[string]interface{}{},
			encodedRequest: "{}\n",
		},
	}

	for _, tt := range tests {
		outbound := transporttest.NewMockOnewayOutbound(mockCtrl)
		client := New(channel.MultiOutbound(caller, service,
			transport.Outbounds{
				Oneway: outbound,
			}))

		if !tt.noCall {
			reqMatcher := transporttest.NewRequestMatcher(t,
				&transport.Request{
					Caller:    caller,
					Service:   service,
					Procedure: tt.procedure,
					Encoding:  Encoding,
					Headers:   transport.Headers(tt.headers),
					Body:      bytes.NewReader([]byte(tt.encodedRequest)),
				})

			if tt.wantErr != "" {
				outbound.
					EXPECT().
					CallOneway(gomock.Any(), reqMatcher).
					Return(nil, errors.New(tt.wantErr))
			} else {
				outbound.
					EXPECT().
					CallOneway(gomock.Any(), reqMatcher).
					Return(&successAck{}, nil)
			}
		}

		ack, err := client.CallOneway(
			ctx,
			yarpc.NewReqMeta().Procedure(tt.procedure).Headers(tt.headers),
			tt.body)

		if tt.wantErr != "" {
			assert.Error(t, err)
			assert.Contains(t, err.Error(), tt.wantErr)
		} else {
			assert.NoError(t, err, "")
			assert.Equal(t, ack.String(), "success")
		}
	}
}