func TestCallOnewayFailure(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() ctx := context.Background() caller := "caller" service := "service" procedure := "procedure" body := []byte{1, 2, 3} outbound := transporttest.NewMockOnewayOutbound(mockCtrl) client := New(channel.MultiOutbound(caller, service, transport.Outbounds{ Oneway: outbound, })) outbound.EXPECT().CallOneway(gomock.Any(), transporttest.NewRequestMatcher(t, &transport.Request{ Service: service, Caller: caller, Procedure: procedure, Encoding: Encoding, Body: bytes.NewReader(body), }), ).Return(nil, errors.New("some error")) _, err := client.CallOneway( ctx, yarpc.NewReqMeta().Procedure(procedure), body) assert.Error(t, err) }
// Phone implements the phone procedure func Phone(ctx context.Context, reqMeta yarpc.ReqMeta, body *PhoneRequest) (*PhoneResponse, yarpc.ResMeta, error) { var outbound transport.UnaryOutbound switch { case body.Transport.HTTP != nil: t := body.Transport.HTTP url := fmt.Sprintf("http://%s:%d", t.Host, t.Port) outbound = ht.NewOutbound(url) case body.Transport.TChannel != nil: t := body.Transport.TChannel hostport := fmt.Sprintf("%s:%d", t.Host, t.Port) ch, err := tchannel.NewChannel("yarpc-test-client", nil) if err != nil { return nil, nil, fmt.Errorf("failed to build TChannel: %v", err) } outbound = tch.NewOutbound(ch, tch.HostPort(hostport)) default: return nil, nil, fmt.Errorf("unconfigured transport") } if err := outbound.Start(transport.NoDeps); err != nil { return nil, nil, err } defer outbound.Stop() // TODO use reqMeta.Service for caller client := json.New(channel.MultiOutbound("yarpc-test", body.Service, transport.Outbounds{ Unary: outbound, })) resBody := PhoneResponse{ Service: "yarpc-test", // TODO use reqMeta.Service Procedure: reqMeta.Procedure(), } ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond) defer cancel() _, err := client.Call( ctx, yarpc.NewReqMeta().Procedure(body.Procedure), body.Body, &resBody.Body) if err != nil { return nil, nil, err } return &resBody, nil, nil }
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 TestClient(t *testing.T) { tests := []struct { desc string giveRequestBody envelope.Enveloper // outgoing request body giveResponseEnvelope *wire.Envelope // returned on DecodeEnveloped() giveResponseBody *wire.Value // return on Decode() clientOptions []ClientOption expectCall bool // whether outbound.Call is expected wantRequestEnvelope *wire.Envelope // expect EncodeEnveloped(x) wantRequestBody *wire.Value // expect Encode(x) wantError string // whether an error is expected }{ { desc: "happy case", clientOptions: []ClientOption{Enveloped}, giveRequestBody: fakeEnveloper(wire.Call), wantRequestEnvelope: &wire.Envelope{ Name: "someMethod", SeqID: 1, Type: wire.Call, Value: wire.NewValueStruct(wire.Struct{}), }, expectCall: true, giveResponseEnvelope: &wire.Envelope{ Name: "someMethod", SeqID: 1, Type: wire.Reply, Value: wire.NewValueStruct(wire.Struct{}), }, }, { desc: "happy case without enveloping", giveRequestBody: fakeEnveloper(wire.Call), wantRequestBody: valueptr(wire.NewValueStruct(wire.Struct{})), expectCall: true, giveResponseBody: valueptr(wire.NewValueStruct(wire.Struct{})), }, { desc: "wrong envelope type for request", clientOptions: []ClientOption{Enveloped}, giveRequestBody: fakeEnveloper(wire.Reply), wantError: `failed to encode "thrift" request body for procedure ` + `"MyService::someMethod" of service "service": unexpected envelope type: Reply`, }, { desc: "TApplicationException", clientOptions: []ClientOption{Enveloped}, giveRequestBody: fakeEnveloper(wire.Call), wantRequestEnvelope: &wire.Envelope{ Name: "someMethod", SeqID: 1, Type: wire.Call, Value: wire.NewValueStruct(wire.Struct{}), }, expectCall: true, giveResponseEnvelope: &wire.Envelope{ Name: "someMethod", SeqID: 1, Type: wire.Exception, Value: wire.NewValueStruct(wire.Struct{Fields: []wire.Field{ {ID: 1, Value: wire.NewValueString("great sadness")}, {ID: 2, Value: wire.NewValueI32(7)}, }}), }, wantError: `thrift request to procedure "MyService::someMethod" of ` + `service "service" encountered an internal failure: ` + "TApplicationException{Message: great sadness, Type: PROTOCOL_ERROR}", }, { desc: "wrong envelope type for response", clientOptions: []ClientOption{Enveloped}, giveRequestBody: fakeEnveloper(wire.Call), wantRequestEnvelope: &wire.Envelope{ Name: "someMethod", SeqID: 1, Type: wire.Call, Value: wire.NewValueStruct(wire.Struct{}), }, expectCall: true, giveResponseEnvelope: &wire.Envelope{ Name: "someMethod", SeqID: 1, Type: wire.Call, Value: wire.NewValueStruct(wire.Struct{}), }, wantError: `failed to decode "thrift" response body for procedure ` + `"MyService::someMethod" of service "service": unexpected envelope type: Call`, }, } for _, tt := range tests { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() proto := NewMockProtocol(mockCtrl) if tt.wantRequestEnvelope != nil { proto.EXPECT().EncodeEnveloped(*tt.wantRequestEnvelope, gomock.Any()). Do(func(_ wire.Envelope, w io.Writer) { _, err := w.Write([]byte("irrelevant")) require.NoError(t, err, "Write() failed") }).Return(nil) } if tt.wantRequestBody != nil { proto.EXPECT().Encode(*tt.wantRequestBody, gomock.Any()). Do(func(_ wire.Value, w io.Writer) { _, err := w.Write([]byte("irrelevant")) require.NoError(t, err, "Write() failed") }).Return(nil) } ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() trans := transporttest.NewMockUnaryOutbound(mockCtrl) if tt.expectCall { trans.EXPECT().Call(ctx, transporttest.NewRequestMatcher(t, &transport.Request{ Caller: "caller", Service: "service", Encoding: Encoding, Procedure: "MyService::someMethod", Body: bytes.NewReader([]byte("irrelevant")), }), ).Return(&transport.Response{ Body: ioutil.NopCloser(bytes.NewReader([]byte("irrelevant"))), }, nil) } if tt.giveResponseEnvelope != nil { proto.EXPECT().DecodeEnveloped(gomock.Any()).Return(*tt.giveResponseEnvelope, nil) } if tt.giveResponseBody != nil { proto.EXPECT().Decode(gomock.Any(), wire.TStruct).Return(*tt.giveResponseBody, nil) } opts := tt.clientOptions opts = append(opts, Protocol(proto)) c := New(Config{ Service: "MyService", Channel: channel.MultiOutbound("caller", "service", transport.Outbounds{ Unary: trans, }), }, opts...) _, _, err := c.Call(ctx, nil, tt.giveRequestBody) if tt.wantError != "" { if assert.Error(t, err, "%v: expected failure", tt.desc) { assert.Contains(t, err.Error(), tt.wantError, "%v: error mismatch", tt.desc) } } else { assert.NoError(t, err, "%v: expected success", tt.desc) } } }
func TestClientOneway(t *testing.T) { caller, service, procedure := "caller", "MyService", "someMethod" tests := []struct { desc string giveRequestBody envelope.Enveloper // outgoing request body clientOptions []ClientOption expectCall bool // whether outbound.Call is expected wantRequestEnvelope *wire.Envelope // expect EncodeEnveloped(x) wantRequestBody *wire.Value // expect Encode(x) wantError string // whether an error is expected }{ { desc: "happy case", giveRequestBody: fakeEnveloper(wire.Call), clientOptions: []ClientOption{Enveloped}, expectCall: true, wantRequestEnvelope: &wire.Envelope{ Name: procedure, SeqID: 1, Type: wire.Call, Value: wire.NewValueStruct(wire.Struct{}), }, }, { desc: "happy case without enveloping", giveRequestBody: fakeEnveloper(wire.Call), expectCall: true, wantRequestBody: valueptr(wire.NewValueStruct(wire.Struct{})), }, { desc: "wrong envelope type for request", giveRequestBody: fakeEnveloper(wire.Reply), clientOptions: []ClientOption{Enveloped}, wantError: `failed to encode "thrift" request body for procedure ` + `"MyService::someMethod" of service "MyService": unexpected envelope type: Reply`, }, } for _, tt := range tests { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() proto := NewMockProtocol(mockCtrl) bodyBytes := []byte("irrelevant") if tt.wantRequestEnvelope != nil { proto.EXPECT().EncodeEnveloped(*tt.wantRequestEnvelope, gomock.Any()). Do(func(_ wire.Envelope, w io.Writer) { _, err := w.Write(bodyBytes) require.NoError(t, err, "Write() failed") }).Return(nil) } if tt.wantRequestBody != nil { proto.EXPECT().Encode(*tt.wantRequestBody, gomock.Any()). Do(func(_ wire.Value, w io.Writer) { _, err := w.Write(bodyBytes) require.NoError(t, err, "Write() failed") }).Return(nil) } ctx := context.Background() onewayOutbound := transporttest.NewMockOnewayOutbound(mockCtrl) requestMatcher := transporttest.NewRequestMatcher(t, &transport.Request{ Caller: caller, Service: service, Encoding: Encoding, Procedure: procedureName(service, procedure), Body: bytes.NewReader(bodyBytes), }) if tt.expectCall { if tt.wantError != "" { onewayOutbound. EXPECT(). CallOneway(ctx, requestMatcher). Return(nil, errors.New(tt.wantError)) } else { onewayOutbound. EXPECT(). CallOneway(ctx, requestMatcher). Return(&successAck{}, nil) } } opts := tt.clientOptions opts = append(opts, Protocol(proto)) c := New(Config{ Service: service, Channel: channel.MultiOutbound(caller, service, transport.Outbounds{ Oneway: onewayOutbound, }), }, opts...) ack, err := c.CallOneway(ctx, nil, tt.giveRequestBody) if tt.wantError != "" { if assert.Error(t, err, "%v: expected failure", tt.desc) { assert.Contains(t, err.Error(), tt.wantError, "%v: error mismatch", tt.desc) } } else { assert.NoError(t, err, "%v: expected success", tt.desc) assert.Equal(t, "success", ack.String()) } } }
func (d dispatcher) Channel(service string) transport.Channel { if rs, ok := d.outbounds[service]; ok { return channel.MultiOutbound(d.Name, service, rs) } panic(noOutboundForService{Service: service}) }
func TestInjectClientSuccess(t *testing.T) { type unknownClient interface{} type knownClient interface{} clear := yarpc.RegisterClientBuilder( func(transport.Channel) knownClient { return knownClient(struct{}{}) }) defer clear() mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() tests := []struct { name string target interface{} // list of services for which Channel() should return successfully knownServices []string // list of field names in target we expect to be nil or non-nil wantNil []string wantNonNil []string }{ { name: "empty", target: &struct{}{}, }, { name: "unknown service non-nil", target: &struct { Client json.Client `service:"foo"` }{ Client: json.New(channel.MultiOutbound( "foo", "bar", transport.Outbounds{ Unary: transporttest.NewMockUnaryOutbound(mockCtrl), })), }, wantNonNil: []string{"Client"}, }, { name: "unknown type untagged", target: &struct { Client unknownClient `notservice:"foo"` }{}, wantNil: []string{"Client"}, }, { name: "unknown type non-nil", target: &struct { Client unknownClient `service:"foo"` }{Client: unknownClient(struct{}{})}, wantNonNil: []string{"Client"}, }, { name: "known type", knownServices: []string{"foo"}, target: &struct { Client knownClient `service:"foo"` }{}, wantNonNil: []string{"Client"}, }, { name: "default encodings", knownServices: []string{"jsontest", "rawtest"}, target: &struct { JSON json.Client `service:"jsontest"` Raw raw.Client `service:"rawtest"` }{}, wantNonNil: []string{"JSON", "Raw"}, }, { name: "unexported field", target: &struct { rawClient raw.Client `service:"rawtest"` }{}, wantNil: []string{"rawClient"}, }, } for _, tt := range tests { cp := newMockChannelProvier(mockCtrl, tt.knownServices...) assert.NotPanics(t, func() { yarpc.InjectClients(cp, tt.target) }, tt.name) for _, fieldName := range tt.wantNil { field := reflect.ValueOf(tt.target).Elem().FieldByName(fieldName) assert.True(t, field.IsNil(), "expected %q to be nil", fieldName) } for _, fieldName := range tt.wantNonNil { field := reflect.ValueOf(tt.target).Elem().FieldByName(fieldName) assert.False(t, field.IsNil(), "expected %q to be non-nil", fieldName) } } }
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") } } }