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) }
func TestRecording(t *testing.T) { tMock := testingTMock{t, 0} dir, err := ioutil.TempDir("", "yarpcgorecorder") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) // clean up recorder := NewRecorder(&tMock, RecordMode(Append), RecordsPath(dir)) withConnectedClient(t, recorder, func(client raw.Client) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() rbody, _, err := client.Call(ctx, yarpc.NewReqMeta().Procedure("hello"), []byte("Hello")) require.NoError(t, err) assert.Equal(t, []byte("Hello, World"), rbody) }) recordPath := path.Join(dir, refRecordFilename) _, err = os.Stat(recordPath) require.NoError(t, err) recordContent, err := ioutil.ReadFile(recordPath) require.NoError(t, err) assert.Equal(t, refRecordContent, string(recordContent)) }
func (h *multiHopHandler) Handle(ctx context.Context, reqMeta yarpc.ReqMeta, body interface{}) (interface{}, yarpc.ResMeta, error) { if h.phoneClient == nil { panic("call SetClient() and SetTransport() first") } assertBaggageMatches(ctx, h.t, h.wantBaggage) span := opentracing.SpanFromContext(ctx) for key, value := range h.addBaggage { span.SetBaggageItem(key, value) } ctx = opentracing.ContextWithSpan(ctx, span) var resp js.RawMessage phoneResMeta, err := h.phoneClient.Call( ctx, yarpc.NewReqMeta().Procedure("phone").Headers(reqMeta.Headers()), &server.PhoneRequest{ Service: "ctxclient", Procedure: h.phoneCallTo, Transport: h.phoneCallTransport, Body: &js.RawMessage{'{', '}'}, }, &resp) resMeta := yarpc.NewResMeta().Headers(phoneResMeta.Headers()) return map[string]interface{}{}, resMeta, err }
// JSON starts an http run using JSON encoding func JSON(t crossdock.T, dispatcher yarpc.Dispatcher) { fatals := crossdock.Fatals(t) client := json.New(dispatcher.Channel("oneway-test")) token := getRandomID() ack, err := client.CallOneway( context.Background(), yarpc.NewReqMeta().Procedure("echo/json"), &jsonToken{Token: token}, ) // ensure channel hasn't been filled yet select { case <-serverCalledBack: fatals.FailNow("oneway json test failed", "client waited for server to fill channel") default: } fatals.NoError(err, "call to oneway/json failed: %v", err) fatals.NotNil(ack, "ack is nil") serverToken := <-serverCalledBack fatals.Equal(token, string(serverToken), "Client/Server token mismatch") }
func TestEndToEnd(t *testing.T) { tMock := testingTMock{t, 0} dir, err := ioutil.TempDir("", "yarpcgorecorder") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) // clean up // First we double check that our cache is empty. recorder := NewRecorder(&tMock, RecordMode(Replay), RecordsPath(dir)) withDisconnectedClient(t, recorder, func(client raw.Client) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() require.Panics(t, func() { client.Call(ctx, yarpc.NewReqMeta().Procedure("hello"), []byte("Hello")) }) assert.Equal(t, tMock.fatalCount, 1) }) // Now let's record our call. recorder = NewRecorder(&tMock, RecordMode(Overwrite), RecordsPath(dir)) withConnectedClient(t, recorder, func(client raw.Client) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() rbody, _, err := client.Call(ctx, yarpc.NewReqMeta().Procedure("hello"), []byte("Hello")) require.NoError(t, err) assert.Equal(t, rbody, []byte("Hello, World")) }) // Now replay the call. recorder = NewRecorder(&tMock, RecordMode(Replay), RecordsPath(dir)) withDisconnectedClient(t, recorder, func(client raw.Client) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() rbody, _, err := client.Call(ctx, yarpc.NewReqMeta().Procedure("hello"), []byte("Hello")) require.NoError(t, err) assert.Equal(t, rbody, []byte("Hello, World")) }) }
func runYARPCClient(b *testing.B, c raw.Client) { for i := 0; i < b.N; i++ { ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() _, _, err := c.Call(ctx, yarpc.NewReqMeta().Procedure("echo"), _reqBody) require.NoError(b, err, "request %d failed", i+1) } }
func (h handler) echoEcho(ctx context.Context) error { var resBody echoResBody _, err := h.client.Call( ctx, yarpc.NewReqMeta().Procedure("echoecho"), &echoReqBody{}, &resBody, ) return err }
func set(ctx context.Context, c json.Client, k string, v string) error { var response setResponse ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() _, err := c.Call( ctx, yarpc.NewReqMeta().Procedure("set"), &setRequest{Key: k, Value: v}, &response, ) return err }
func rawCall(dispatcher yarpc.Dispatcher, headers yarpc.Headers, procedure string, token []byte) ([]byte, yarpc.CallResMeta, error) { client := raw.New(dispatcher.Channel(serverName)) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() reqMeta := yarpc.NewReqMeta().Procedure(procedure).Headers(headers) resBody, resMeta, err := client.Call(ctx, reqMeta, token) return resBody, resMeta, err }
func get(ctx context.Context, c json.Client, k string) (string, error) { var response getResponse ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() _, err := c.Call( ctx, yarpc.NewReqMeta().Procedure("get"), &getRequest{Key: k}, &response, ) return response.Value, err }
func jsonCall(dispatcher yarpc.Dispatcher, headers yarpc.Headers, token string) (string, yarpc.CallResMeta, error) { client := json.New(dispatcher.Channel(serverName)) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() reqMeta := yarpc.NewReqMeta().Procedure("echo").Headers(headers) reqBody := &jsonEcho{Token: token} var resBody jsonEcho resMeta, err := client.Call(ctx, reqMeta, reqBody, &resBody) return resBody.Token, resMeta, err }
func (c rawCaller) Call(h yarpc.Headers) (yarpc.Headers, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() _, res, err := c.c.Call( ctx, yarpc.NewReqMeta().Headers(h).Procedure("echo/raw"), []byte("hello")) if err != nil { return yarpc.Headers{}, err } return res.Headers(), nil }
func (c thriftCaller) Call(h yarpc.Headers) (yarpc.Headers, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() _, res, err := c.c.Echo( ctx, yarpc.NewReqMeta().Headers(h), &echo.Ping{Beep: "hello"}) if err != nil { return yarpc.Headers{}, err } return res.Headers(), nil }
func (h handler) handleEchoEcho(ctx context.Context, reqMeta yarpc.ReqMeta, reqBody *echoReqBody) (*echoResBody, yarpc.ResMeta, error) { h.assertBaggage(ctx) var resBody echoResBody _, err := h.client.Call( ctx, yarpc.NewReqMeta().Procedure("echo"), reqBody, &resBody, ) if err != nil { return nil, nil, err } return &resBody, nil, nil }
func (c jsonCaller) Call(h yarpc.Headers) (yarpc.Headers, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() var resBody interface{} res, err := c.c.Call( ctx, yarpc.NewReqMeta().Headers(h).Procedure("echo"), map[string]interface{}{}, &resBody) if err != nil { return yarpc.Headers{}, err } return res.Headers(), nil }
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 thriftCall(dispatcher yarpc.Dispatcher, headers yarpc.Headers, token string) (string, yarpc.CallResMeta, error) { client := echoclient.New(dispatcher.Channel(serverName)) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() reqMeta := yarpc.NewReqMeta().Headers(headers) ping := &echo.Ping{Beep: token} resBody, resMeta, err := client.Echo(ctx, reqMeta, ping) if err != nil { return "", nil, err } return resBody.Boop, resMeta, 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 }
// Raw implements the 'raw' behavior. func Raw(t crossdock.T) { t = createEchoT("raw", t) fatals := crossdock.Fatals(t) dispatcher := disp.Create(t) fatals.NoError(dispatcher.Start(), "could not start Dispatcher") defer dispatcher.Stop() client := raw.New(dispatcher.Channel("yarpc-test")) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() token := random.Bytes(5) resBody, _, err := client.Call(ctx, yarpc.NewReqMeta().Procedure("echo/raw"), token) crossdock.Fatals(t).NoError(err, "call to echo/raw failed: %v", err) crossdock.Assert(t).True(bytes.Equal(token, resBody), "server said: %v", resBody) }
func TestEmptyReplay(t *testing.T) { tMock := testingTMock{t, 0} dir, err := ioutil.TempDir("", "yarpcgorecorder") if err != nil { t.Fatal(err) } defer os.RemoveAll(dir) // clean up recorder := NewRecorder(&tMock, RecordMode(Replay), RecordsPath(dir)) withDisconnectedClient(t, recorder, func(client raw.Client) { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() require.Panics(t, func() { client.Call(ctx, yarpc.NewReqMeta().Procedure("hello"), []byte("Hello")) }) assert.Equal(t, tMock.fatalCount, 1) }) }
// Raw starts an http run using raw encoding func Raw(t crossdock.T, dispatcher yarpc.Dispatcher) { fatals := crossdock.Fatals(t) client := raw.New(dispatcher.Channel("oneway-test")) ctx := context.Background() token := []byte(getRandomID()) ack, err := client.CallOneway(ctx, yarpc.NewReqMeta().Procedure("echo/raw"), token) // ensure channel hasn't been filled yet select { case <-serverCalledBack: fatals.FailNow("oneway raw test failed", "client waited for server to fill channel") default: } fatals.NoError(err, "call to oneway/raw failed: %v", err) fatals.NotNil(ack, "ack is nil") serverToken := <-serverCalledBack fatals.Equal(token, serverToken, "Client/Server token mismatch.") }
// runRaw tests if a yarpc client returns a remote timeout error behind the // TimeoutError interface when a remote http handler returns a handler timeout. func runRaw(t crossdock.T, disp yarpc.Dispatcher) { assert := crossdock.Assert(t) fatals := crossdock.Fatals(t) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() ch := raw.New(disp.Channel("yarpc-test")) _, _, err := ch.Call(ctx, yarpc.NewReqMeta().Procedure("handlertimeout/raw"), nil) fatals.Error(err, "expected an error") if transport.IsBadRequestError(err) { t.Skipf("handlertimeout/raw method 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()) }
// JSON implements the 'json' behavior. func JSON(t crossdock.T) { t = createEchoT("json", t) fatals := crossdock.Fatals(t) dispatcher := disp.Create(t) fatals.NoError(dispatcher.Start(), "could not start Dispatcher") defer dispatcher.Stop() client := json.New(dispatcher.Channel("yarpc-test")) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() var response jsonEcho token := random.String(5) _, err := client.Call( ctx, yarpc.NewReqMeta().Procedure("echo"), &jsonEcho{Token: token}, &response, ) crossdock.Fatals(t).NoError(err, "call to echo failed: %v", err) crossdock.Assert(t).Equal(token, response.Token, "server said: %v", response.Token) }
// Run tests if a yarpc client returns correctly a client timeout error behind // the TimeoutError interface when the context deadline is reached while the // server is taking too long to respond. func Run(t crossdock.T) { assert := crossdock.Assert(t) fatals := crossdock.Fatals(t) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() dispatcher := disp.Create(t) fatals.NoError(dispatcher.Start(), "could not start Dispatcher") defer dispatcher.Stop() ch := raw.New(dispatcher.Channel("yarpc-test")) _, _, err := ch.Call(ctx, yarpc.NewReqMeta().Procedure("sleep/raw"), nil) fatals.Error(err, "expected a failure for timeout") if transport.IsBadRequestError(err) { t.Skipf("sleep/raw method not implemented: %v", err) return } assert.True(transport.IsTimeoutError(err), "returns a TimeoutError: %T", err) trans := t.Param(params.Transport) switch trans { case "http": form := strings.HasPrefix(err.Error(), `client timeout for procedure "sleep/raw" of service "yarpc-test" after`) assert.True(form, "should be a client timeout: %q", err.Error()) case "tchannel": form := strings.HasPrefix(err.Error(), `timeout`) assert.True(form, "should be a remote timeout (we cant represent client timeout with tchannel): %q", err.Error()) default: fatals.Fail("", "unknown transport %q", trans) } }
func TestHandlerPanic(t *testing.T) { inbound := NewInbound("localhost:0") serverDispatcher := yarpc.NewDispatcher(yarpc.Config{ Name: "yarpc-test", Inbounds: []transport.Inbound{inbound}, }) serverDispatcher.Register([]transport.Registrant{ { Procedure: "panic", HandlerSpec: transport.NewUnaryHandlerSpec(panickedHandler{}), }, }) require.NoError(t, serverDispatcher.Start()) defer serverDispatcher.Stop() clientDispatcher := yarpc.NewDispatcher(yarpc.Config{ Name: "yarpc-test-client", Outbounds: yarpc.Outbounds{ "yarpc-test": { Unary: NewOutbound(fmt.Sprintf("http://%s", inbound.Addr().String())), }, }, }) require.NoError(t, clientDispatcher.Start()) defer clientDispatcher.Stop() client := raw.New(clientDispatcher.Channel("yarpc-test")) ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() _, _, err := client.Call(ctx, yarpc.NewReqMeta().Procedure("panic"), []byte{}) assert.True(t, transport.IsUnexpectedError(err), "Must be an UnexpectedError") assert.Equal(t, `UnexpectedError: error for procedure "panic" of service "yarpc-test": panic: oops I panicked!`, err.Error()) }
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") } } }
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) } } } }
// RunGauntlet takes an rpc object and runs the gauntlet func RunGauntlet(t crossdock.T, c Config) { checks := crossdock.Checks(t) if c.Services == 0 { c.Services = AllServices } bytesToken := random.Bytes(10) tests := []TT{ { Function: "TestBinary", Give: []interface{}{bytesToken}, Want: bytesToken, }, { Function: "TestByte", Give: []interface{}{ptr.Int8(42)}, Want: int8(42), }, { Function: "TestDouble", Give: []interface{}{ptr.Float64(12.34)}, Want: float64(12.34), }, { Function: "TestEnum", Details: "MyNumberz", Give: []interface{}{numberzp(gauntlet.MyNumberz)}, Want: gauntlet.MyNumberz, }, { Function: "TestEnum", Details: "NumberzThree", Give: []interface{}{numberzp(gauntlet.NumberzThree)}, Want: gauntlet.NumberzThree, }, { Function: "TestEnum", Details: "unrecognized Numberz", Give: []interface{}{numberzp(gauntlet.Numberz(42))}, Want: gauntlet.Numberz(42), }, { Function: "TestException", Details: "Xception", Give: []interface{}{ptr.String("Xception")}, WantError: &gauntlet.Xception{ ErrorCode: ptr.Int32(1001), Message: ptr.String("Xception"), }, }, { Function: "TestException", Details: "TException", Give: []interface{}{ptr.String("TException")}, WantErrorLike: "great sadness", }, { Function: "TestException", Details: "no error", Give: []interface{}{ptr.String("yolo")}, }, { Function: "TestI32", Give: []interface{}{ptr.Int32(123)}, Want: int32(123), }, { Function: "TestI64", Give: []interface{}{ptr.Int64(18934714)}, Want: int64(18934714), }, { Function: "TestInsanity", Give: []interface{}{ &gauntlet.Insanity{ UserMap: map[gauntlet.Numberz]gauntlet.UserId{ gauntlet.NumberzThree: gauntlet.UserId(100), gauntlet.Numberz(100): gauntlet.UserId(200), }, Xtructs: []*gauntlet.Xtruct{ {StringThing: ptr.String("0")}, {ByteThing: ptr.Int8(1)}, {I32Thing: ptr.Int32(2)}, {I64Thing: ptr.Int64(3)}, }, }, }, Want: map[gauntlet.UserId]map[gauntlet.Numberz]*gauntlet.Insanity{ 1: { gauntlet.NumberzTwo: &gauntlet.Insanity{ UserMap: map[gauntlet.Numberz]gauntlet.UserId{ gauntlet.NumberzThree: gauntlet.UserId(100), gauntlet.Numberz(100): gauntlet.UserId(200), }, Xtructs: []*gauntlet.Xtruct{ {StringThing: ptr.String("0")}, {ByteThing: ptr.Int8(1)}, {I32Thing: ptr.Int32(2)}, {I64Thing: ptr.Int64(3)}, }, }, gauntlet.NumberzThree: &gauntlet.Insanity{ UserMap: map[gauntlet.Numberz]gauntlet.UserId{ gauntlet.NumberzThree: gauntlet.UserId(100), gauntlet.Numberz(100): gauntlet.UserId(200), }, Xtructs: []*gauntlet.Xtruct{ {StringThing: ptr.String("0")}, {ByteThing: ptr.Int8(1)}, {I32Thing: ptr.Int32(2)}, {I64Thing: ptr.Int64(3)}, }, }, }, 2: { gauntlet.NumberzSix: &gauntlet.Insanity{}, }, }, }, { Function: "TestList", Give: []interface{}{[]int32{1, 2, 3}}, Want: []int32{1, 2, 3}, }, { Function: "TestMap", Give: []interface{}{map[int32]int32{1: 2, 3: 4, 5: 6}}, Want: map[int32]int32{1: 2, 3: 4, 5: 6}, }, { Function: "TestMapMap", Give: []interface{}{ptr.Int32(42)}, Want: map[int32]map[int32]int32{ -4: { -4: -4, -3: -3, -2: -2, -1: -1, }, 4: { 1: 1, 2: 2, 3: 3, 4: 4, }, }, }, { Function: "TestMulti", Give: []interface{}{ ptr.Int8(100), ptr.Int32(200), ptr.Int64(300), map[int16]string{1: "1", 2: "2", 3: "3"}, numberzp(gauntlet.NumberzEight), useridp(42), }, Want: &gauntlet.Xtruct{ StringThing: ptr.String("Hello2"), ByteThing: ptr.Int8(100), I32Thing: ptr.Int32(200), I64Thing: ptr.Int64(300), }, }, { Function: "TestMultiException", Details: "Xception", Give: []interface{}{ptr.String("Xception"), ptr.String("foo")}, WantError: &gauntlet.Xception{ ErrorCode: ptr.Int32(1001), Message: ptr.String("This is an Xception"), }, }, { Function: "TestMultiException", Details: "Xception2", Give: []interface{}{ptr.String("Xception2"), ptr.String("foo")}, WantError: &gauntlet.Xception2{ ErrorCode: ptr.Int32(2002), StructThing: &gauntlet.Xtruct{StringThing: ptr.String("foo")}, }, }, { Function: "TestMultiException", Details: "no error", Give: []interface{}{ptr.String("hello"), ptr.String("foo")}, Want: &gauntlet.Xtruct{StringThing: ptr.String("foo")}, }, { Function: "TestNest", Give: []interface{}{ &gauntlet.Xtruct2{ ByteThing: ptr.Int8(-1), I32Thing: ptr.Int32(-1234), StructThing: &gauntlet.Xtruct{ StringThing: ptr.String("0"), ByteThing: ptr.Int8(1), I32Thing: ptr.Int32(2), I64Thing: ptr.Int64(3), }, }, }, Want: &gauntlet.Xtruct2{ ByteThing: ptr.Int8(-1), I32Thing: ptr.Int32(-1234), StructThing: &gauntlet.Xtruct{ StringThing: ptr.String("0"), ByteThing: ptr.Int8(1), I32Thing: ptr.Int32(2), I64Thing: ptr.Int64(3), }, }, }, { Function: "TestSet", Give: []interface{}{ map[int32]struct{}{ 1: {}, 2: {}, -1: {}, -2: {}, }, }, Want: map[int32]struct{}{ 1: {}, 2: {}, -1: {}, -2: {}, }, }, { Function: "TestString", Give: []interface{}{ptr.String("hello")}, Want: "hello", }, { Function: "TestStringMap", Give: []interface{}{ map[string]string{ "foo": "bar", "hello": "world", }, }, Want: map[string]string{ "foo": "bar", "hello": "world", }, }, { Function: "TestStruct", Give: []interface{}{ &gauntlet.Xtruct{ StringThing: ptr.String("0"), ByteThing: ptr.Int8(1), I32Thing: ptr.Int32(2), I64Thing: ptr.Int64(3), }, }, Want: &gauntlet.Xtruct{ StringThing: ptr.String("0"), ByteThing: ptr.Int8(1), I32Thing: ptr.Int32(2), I64Thing: ptr.Int64(3), }, }, { Function: "TestTypedef", Give: []interface{}{useridp(42)}, Want: gauntlet.UserId(42), }, { Function: "TestVoid", Give: []interface{}{}, }, { Function: "TestOneway", Oneway: true, Give: []interface{}{ptr.Int32(123)}, WantError: nil, }, { Service: "SecondService", Function: "BlahBlah", Give: []interface{}{}, }, { Service: "SecondService", Function: "SecondtestString", Give: []interface{}{ptr.String("hello")}, Want: "hello", }, } for _, tt := range tests { if tt.Service == "" { tt.Service = "ThriftTest" } switch tt.Service { case "ThriftTest": if c.Services&ThriftTest == 0 { continue } case "SecondService": if c.Services&SecondService == 0 { continue } } t.Tag("service", tt.Service) t.Tag("function", tt.Function) //only run oneway tests if specified if !c.EnableOneway && tt.Oneway { continue } desc := BuildDesc(tt) client := buildClient(t, desc, tt.Service, c) f := client.MethodByName(tt.Function) if !checks.True(f.IsValid(), "%v: invalid function", desc) { continue } ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() args := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(yarpc.NewReqMeta())} if give, ok := BuildArgs(t, desc, f.Type(), tt.Give, 2); ok { args = append(args, give...) } else { continue } got, err := extractCallResponse(t, desc, f.Call(args)) if isUnrecognizedProcedure(err) { t.Skipf("%v: procedure not defined", desc) continue } Assert(t, tt, desc, got, err) } }
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()) } } }