Example #1
0
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)
}
Example #2
0
// 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
}
Example #3
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())
			}
		}
	}
}
Example #4
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())
		}
	}
}
Example #5
0
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)
		}
	}
}
Example #6
0
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())
		}
	}
}
Example #7
0
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})
}
Example #8
0
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)
		}
	}
}
Example #9
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)
			}
		}
	}
}
Example #10
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")
		}
	}
}