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()) } }
// 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 }
// 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()) }
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()) } }
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() }
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") } }
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") } }
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) }
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, }) }
// 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) } } }
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()) } } } }
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()) } } }
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) } } } }
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) } } } }
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") } } }