Example #1
0
func (r staticRegistry) GetHandlerSpec(service string, procedure string) (transport.HandlerSpec, error) {
	if procedure == testProcedure {
		return transport.NewUnaryHandlerSpec(r.Handler), nil
	} else {
		return transport.NewOnewayHandlerSpec(r.OnewayHandler), nil
	}
}
Example #2
0
// Procedure builds a Registrant from the given raw handler.
func Procedure(name string, handler UnaryHandler) []transport.Registrant {
	return []transport.Registrant{
		{
			Procedure:   name,
			HandlerSpec: transport.NewUnaryHandlerSpec(rawUnaryHandler{handler}),
		},
	}
}
Example #3
0
func TestMapRegistry_ServiceProcedures(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	m := transport.NewMapRegistry("myservice")

	bar := transporttest.NewMockUnaryHandler(mockCtrl)
	foo := transporttest.NewMockUnaryHandler(mockCtrl)
	aww := transporttest.NewMockUnaryHandler(mockCtrl)
	m.Register([]transport.Registrant{
		{
			Service:     "anotherservice",
			Procedure:   "bar",
			HandlerSpec: transport.NewUnaryHandlerSpec(bar),
		},
		{
			Procedure:   "foo",
			HandlerSpec: transport.NewUnaryHandlerSpec(foo),
		},
		{
			Service:     "anotherservice",
			Procedure:   "aww",
			HandlerSpec: transport.NewUnaryHandlerSpec(aww),
		},
	})

	expectedOrderedServiceProcedures := []transport.ServiceProcedure{
		{
			Service:   "anotherservice",
			Procedure: "aww",
		},
		{
			Service:   "anotherservice",
			Procedure: "bar",
		},
		{
			Service:   "myservice",
			Procedure: "foo",
		},
	}

	serviceProcedures := m.ServiceProcedures()

	assert.Equal(t, expectedOrderedServiceProcedures, serviceProcedures)
}
Example #4
0
func TestMapRegistry(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	m := transport.NewMapRegistry("myservice")

	foo := transporttest.NewMockUnaryHandler(mockCtrl)
	bar := transporttest.NewMockUnaryHandler(mockCtrl)
	m.Register([]transport.Registrant{
		{
			Procedure:   "foo",
			HandlerSpec: transport.NewUnaryHandlerSpec(foo),
		},
		{
			Service:     "anotherservice",
			Procedure:   "bar",
			HandlerSpec: transport.NewUnaryHandlerSpec(bar),
		},
	})

	tests := []struct {
		service, procedure string
		want               transport.UnaryHandler
	}{
		{"myservice", "foo", foo},
		{"", "foo", foo},
		{"anotherservice", "foo", nil},
		{"", "bar", nil},
		{"myservice", "bar", nil},
		{"anotherservice", "bar", bar},
	}

	for _, tt := range tests {
		got, err := m.GetHandlerSpec(tt.service, tt.procedure)
		if tt.want != nil {
			assert.NoError(t, err,
				"GetHandlerSpec(%q, %q) failed", tt.service, tt.procedure)
			assert.True(t, tt.want == got.Unary(), // want == match, not deep equals
				"GetHandlerSpec(%q, %q) did not match", tt.service, tt.procedure)
		} else {
			assert.Error(t, err)
		}
	}
}
Example #5
0
// Procedure builds a Registrant from the given JSON handler. handler must be
// a function with a signature similar to,
//
// 	f(ctx context.Context, reqMeta yarpc.ReqMeta, body $reqBody) ($resBody, yarpc.ResMeta, error)
//
// Where $reqBody and $resBody are a map[string]interface{} or pointers to
// structs.
func Procedure(name string, handler interface{}) []transport.Registrant {
	return []transport.Registrant{
		{
			Procedure: name,
			HandlerSpec: transport.NewUnaryHandlerSpec(
				wrapUnaryHandler(name, handler),
			),
		},
	}
}
Example #6
0
func TestHandlerInternalFailure(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	headers := make(http.Header)
	headers.Set(CallerHeader, "somecaller")
	headers.Set(EncodingHeader, "raw")
	headers.Set(TTLMSHeader, "1000")
	headers.Set(ProcedureHeader, "hello")
	headers.Set(ServiceHeader, "fake")

	request := http.Request{
		Method: "POST",
		Header: headers,
		Body:   ioutil.NopCloser(bytes.NewReader([]byte{})),
	}

	rpcHandler := transporttest.NewMockUnaryHandler(mockCtrl)
	rpcHandler.EXPECT().Handle(
		transporttest.NewContextMatcher(t, transporttest.ContextTTL(time.Second)),
		transporttest.NewRequestMatcher(
			t, &transport.Request{
				Caller:    "somecaller",
				Service:   "fake",
				Encoding:  raw.Encoding,
				Procedure: "hello",
				Body:      bytes.NewReader([]byte{}),
			},
		),
		gomock.Any(),
	).Return(fmt.Errorf("great sadness"))

	registry := transporttest.NewMockRegistry(mockCtrl)
	spec := transport.NewUnaryHandlerSpec(rpcHandler)

	registry.EXPECT().GetHandlerSpec("fake", "hello").Return(spec, nil)

	httpHandler := handler{Registry: registry}
	httpResponse := httptest.NewRecorder()
	httpHandler.ServeHTTP(httpResponse, &request)

	code := httpResponse.Code
	assert.True(t, code >= 500 && code < 600, "expected 500 level response")
	assert.Equal(t,
		`UnexpectedError: error for procedure "hello" of service "fake": great sadness`+"\n",
		httpResponse.Body.String())
}
Example #7
0
func TestHandlerSucces(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	headers := make(http.Header)
	headers.Set(CallerHeader, "moe")
	headers.Set(EncodingHeader, "raw")
	headers.Set(TTLMSHeader, "1000")
	headers.Set(ProcedureHeader, "nyuck")
	headers.Set(ServiceHeader, "curly")

	registry := transporttest.NewMockRegistry(mockCtrl)
	rpcHandler := transporttest.NewMockUnaryHandler(mockCtrl)
	spec := transport.NewUnaryHandlerSpec(rpcHandler)

	registry.EXPECT().GetHandlerSpec("curly", "nyuck").Return(spec, nil)

	rpcHandler.EXPECT().Handle(
		transporttest.NewContextMatcher(t,
			transporttest.ContextTTL(time.Second),
		),
		transporttest.NewRequestMatcher(
			t, &transport.Request{
				Caller:    "moe",
				Service:   "curly",
				Encoding:  raw.Encoding,
				Procedure: "nyuck",
				Body:      bytes.NewReader([]byte("Nyuck Nyuck")),
			},
		),
		gomock.Any(),
	).Return(nil)

	httpHandler := handler{Registry: registry}
	req := &http.Request{
		Method: "POST",
		Header: headers,
		Body:   ioutil.NopCloser(bytes.NewReader([]byte("Nyuck Nyuck"))),
	}
	rw := httptest.NewRecorder()
	httpHandler.ServeHTTP(rw, req)
	code := rw.Code
	assert.Equal(t, code, 200, "expected 200 code")
	assert.Equal(t, rw.Body.String(), "")
}
Example #8
0
// BuildRegistrants builds a list of Registrants from a Thrift service
// specification.
func BuildRegistrants(s Service, opts ...RegisterOption) []transport.Registrant {
	var rc registerConfig
	for _, opt := range opts {
		opt.applyRegisterOption(&rc)
	}

	proto := protocol.Binary
	if rc.Protocol != nil {
		proto = rc.Protocol
	}

	rs := make([]transport.Registrant, 0, len(s.Methods))

	// unary procedures
	for methodName, handler := range s.Methods {
		spec := transport.NewUnaryHandlerSpec(thriftUnaryHandler{
			UnaryHandler: handler,
			Protocol:     proto,
			Enveloping:   rc.Enveloping,
		})

		rs = append(rs, transport.Registrant{
			Procedure:   procedureName(s.Name, methodName),
			HandlerSpec: spec,
		})
	}

	// oneway procedures
	for methodName, handler := range s.OnewayMethods {
		spec := transport.NewOnewayHandlerSpec(thriftOnewayHandler{
			OnewayHandler: handler,
			Protocol:      proto,
			Enveloping:    rc.Enveloping,
		})

		rs = append(rs, transport.Registrant{
			Procedure:   procedureName(s.Name, methodName),
			HandlerSpec: spec,
		})
	}

	return rs
}
Example #9
0
func (d dispatcher) Register(rs []transport.Registrant) {
	registrants := make([]transport.Registrant, 0, len(rs))

	for _, r := range rs {
		switch r.HandlerSpec.Type() {
		case transport.Unary:
			h := transport.ApplyInterceptor(r.HandlerSpec.Unary(), d.Interceptor)
			r.HandlerSpec = transport.NewUnaryHandlerSpec(h)
		case transport.Oneway:
			//TODO(apb): add oneway interceptors https://github.com/yarpc/yarpc-go/issues/413
		default:
			panic(fmt.Sprintf("unknown handler type %q for service %q, procedure %q",
				r.HandlerSpec.Type(), r.Service, r.Procedure))
		}

		registrants = append(registrants, r)
	}

	d.Registrar.Register(registrants)
}
Example #10
0
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())
}
Example #11
0
func TestHandlerHeaders(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	tests := []struct {
		giveHeaders http.Header

		wantTTL     time.Duration
		wantHeaders map[string]string
	}{
		{
			giveHeaders: http.Header{
				TTLMSHeader:      {"1000"},
				"Rpc-Header-Foo": {"bar"},
			},
			wantTTL: time.Second,
			wantHeaders: map[string]string{
				"foo": "bar",
			},
		},
		{
			giveHeaders: http.Header{
				TTLMSHeader: {"100"},
				"Rpc-Foo":   {"ignored"},
			},
			wantTTL:     100 * time.Millisecond,
			wantHeaders: map[string]string{},
		},
	}

	for _, tt := range tests {
		registry := transporttest.NewMockRegistry(mockCtrl)
		rpcHandler := transporttest.NewMockUnaryHandler(mockCtrl)
		spec := transport.NewUnaryHandlerSpec(rpcHandler)

		registry.EXPECT().GetHandlerSpec("service", "hello").Return(spec, nil)

		httpHandler := handler{Registry: registry}

		rpcHandler.EXPECT().Handle(
			transporttest.NewContextMatcher(t,
				transporttest.ContextTTL(tt.wantTTL),
			),
			transporttest.NewRequestMatcher(t,
				&transport.Request{
					Caller:    "caller",
					Service:   "service",
					Encoding:  raw.Encoding,
					Procedure: "hello",
					Headers:   transport.HeadersFromMap(tt.wantHeaders),
					Body:      bytes.NewReader([]byte("world")),
				}),
			gomock.Any(),
		).Return(nil)

		headers := http.Header{}
		for k, vs := range tt.giveHeaders {
			for _, v := range vs {
				headers.Add(k, v)
			}
		}
		headers.Set(CallerHeader, "caller")
		headers.Set(ServiceHeader, "service")
		headers.Set(EncodingHeader, "raw")
		headers.Set(ProcedureHeader, "hello")

		req := &http.Request{
			Method: "POST",
			Header: headers,
			Body:   ioutil.NopCloser(bytes.NewReader([]byte("world"))),
		}
		rw := httptest.NewRecorder()
		httpHandler.ServeHTTP(rw, req)
		assert.Equal(t, 200, rw.Code, "expected 200 status code")
	}
}
Example #12
0
func TestHandlerFailures(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	service, procedure := "fake", "hello"

	baseHeaders := make(http.Header)
	baseHeaders.Set(CallerHeader, "somecaller")
	baseHeaders.Set(EncodingHeader, "raw")
	baseHeaders.Set(TTLMSHeader, "1000")
	baseHeaders.Set(ProcedureHeader, procedure)
	baseHeaders.Set(ServiceHeader, service)

	headersWithBadTTL := headerCopyWithout(baseHeaders, TTLMSHeader)
	headersWithBadTTL.Set(TTLMSHeader, "not a number")

	tests := []struct {
		req *http.Request
		msg string

		// if we expect an error as a result of the TTL
		errTTL bool
	}{
		{req: &http.Request{Method: "GET"}, msg: "404 page not found\n"},
		{
			req: &http.Request{
				Method: "POST",
				Header: headerCopyWithout(baseHeaders, CallerHeader),
			},
			msg: "BadRequest: missing caller name\n",
		},
		{
			req: &http.Request{
				Method: "POST",
				Header: headerCopyWithout(baseHeaders, ServiceHeader),
			},
			msg: "BadRequest: missing service name\n",
		},
		{
			req: &http.Request{
				Method: "POST",
				Header: headerCopyWithout(baseHeaders, ProcedureHeader),
			},
			msg: "BadRequest: missing procedure\n",
		},
		{
			req: &http.Request{
				Method: "POST",
				Header: headerCopyWithout(baseHeaders, TTLMSHeader),
			},
			msg:    "BadRequest: missing TTL\n",
			errTTL: true,
		},
		{
			req: &http.Request{
				Method: "POST",
			},
			msg: "BadRequest: missing service name, procedure, caller name, and encoding\n",
		},
		{
			req: &http.Request{
				Method: "POST",
				Header: headersWithBadTTL,
			},
			msg:    `BadRequest: invalid TTL "not a number" for procedure "hello" of service "fake": must be positive integer` + "\n",
			errTTL: true,
		},
	}

	for _, tt := range tests {
		req := tt.req
		if req.Body == nil {
			req.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
		}

		reg := transporttest.NewMockRegistry(mockCtrl)

		if tt.errTTL {
			// since TTL is checked after we've determined the transport type, if we have an
			// error with TTL it will be discovered after we read from the registry
			spec := transport.NewUnaryHandlerSpec(panickedHandler{})
			reg.EXPECT().GetHandlerSpec(service, procedure).Return(spec, nil)
		}

		h := handler{Registry: reg}

		rw := httptest.NewRecorder()
		h.ServeHTTP(rw, tt.req)

		code := rw.Code
		assert.True(t, code >= 400 && code < 500, "expected 400 level code")
		assert.Equal(t, rw.Body.String(), tt.msg)
	}
}
Example #13
0
func TestInboundMux(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	mux := http.NewServeMux()
	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("healthy"))
	})

	i := NewInbound(":0", Mux("/rpc/v1", mux))
	h := transporttest.NewMockUnaryHandler(mockCtrl)
	reg := transporttest.NewMockRegistry(mockCtrl)
	require.NoError(t, i.Start(transport.ServiceDetail{Name: "foo", Registry: reg}, transport.NoDeps))

	defer i.Stop()

	addr := fmt.Sprintf("http://%v/", i.Addr().String())
	resp, err := http.Get(addr + "health")
	if assert.NoError(t, err, "/health failed") {
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if assert.NoError(t, err, "/health body read error") {
			assert.Equal(t, "healthy", string(body), "/health body mismatch")
		}
	}

	// this should fail
	o := NewOutbound(addr)
	require.NoError(t, o.Start(transport.NoDeps), "failed to start outbound")
	defer o.Stop()

	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel()
	_, err = o.Call(ctx, &transport.Request{
		Caller:    "foo",
		Service:   "bar",
		Procedure: "hello",
		Encoding:  raw.Encoding,
		Body:      bytes.NewReader([]byte("derp")),
	})

	if assert.Error(t, err, "RPC call to / should have failed") {
		assert.Equal(t, err.Error(), "404 page not found")
	}

	o = NewOutbound(addr + "rpc/v1")
	require.NoError(t, o.Start(transport.NoDeps), "failed to start outbound")
	defer o.Stop()

	spec := transport.NewUnaryHandlerSpec(h)
	reg.EXPECT().GetHandlerSpec("bar", "hello").Return(spec, nil)

	h.EXPECT().Handle(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)

	res, err := o.Call(ctx, &transport.Request{
		Caller:    "foo",
		Service:   "bar",
		Procedure: "hello",
		Encoding:  raw.Encoding,
		Body:      bytes.NewReader([]byte("derp")),
	})

	if assert.NoError(t, err, "expected rpc request to succeed") {
		defer res.Body.Close()
		s, err := ioutil.ReadAll(res.Body)
		if assert.NoError(t, err) {
			assert.Empty(t, s)
		}
	}
}
Example #14
0
func TestHandlerErrors(t *testing.T) {
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	tests := []struct {
		format  tchannel.Format
		headers []byte

		wantHeaders map[string]string
	}{
		{
			format:      tchannel.JSON,
			headers:     []byte(`{"Rpc-Header-Foo": "bar"}`),
			wantHeaders: map[string]string{"rpc-header-foo": "bar"},
		},
		{
			format: tchannel.Thrift,
			headers: []byte{
				0x00, 0x01, // 1 header
				0x00, 0x03, 'F', 'o', 'o', // Foo
				0x00, 0x03, 'B', 'a', 'r', // Bar
			},
			wantHeaders: map[string]string{"foo": "Bar"},
		},
	}

	for _, tt := range tests {
		rpcHandler := transporttest.NewMockUnaryHandler(mockCtrl)
		registry := transporttest.NewMockRegistry(mockCtrl)

		spec := transport.NewUnaryHandlerSpec(rpcHandler)
		tchHandler := handler{Registry: registry}

		registry.EXPECT().GetHandlerSpec("service", "hello").Return(spec, nil)

		rpcHandler.EXPECT().Handle(
			transporttest.NewContextMatcher(t),
			transporttest.NewRequestMatcher(t,
				&transport.Request{
					Caller:    "caller",
					Service:   "service",
					Headers:   transport.HeadersFromMap(tt.wantHeaders),
					Encoding:  transport.Encoding(tt.format),
					Procedure: "hello",
					Body:      bytes.NewReader([]byte("world")),
				}),
			gomock.Any(),
		).Return(nil)

		respRecorder := newResponseRecorder()

		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()
		tchHandler.handle(ctx, &fakeInboundCall{
			service: "service",
			caller:  "caller",
			format:  tt.format,
			method:  "hello",
			arg2:    tt.headers,
			arg3:    []byte("world"),
			resp:    respRecorder,
		})

		assert.NoError(t, respRecorder.systemErr, "did not expect an error")
	}
}
Example #15
0
func TestHandlerFailures(t *testing.T) {
	tests := []struct {
		desc string

		// context to use in the callm a default one is used otherwise.
		ctx     context.Context
		ctxFunc func() (context.Context, context.CancelFunc)

		sendCall   *fakeInboundCall
		expectCall func(*transporttest.MockUnaryHandler)

		wantErrors []string               // error message contents
		wantStatus tchannel.SystemErrCode // expected status
	}{
		{
			desc: "no timeout on context",
			ctx:  context.Background(),
			sendCall: &fakeInboundCall{
				service: "foo",
				caller:  "bar",
				method:  "hello",
				format:  tchannel.Raw,
				arg2:    []byte{0x00, 0x00},
				arg3:    []byte{0x00},
			},
			wantErrors: []string{"timeout required"},
			wantStatus: tchannel.ErrCodeBadRequest,
		},
		{
			desc: "arg2 reader error",
			sendCall: &fakeInboundCall{
				service: "foo",
				caller:  "bar",
				method:  "hello",
				format:  tchannel.Raw,
				arg2:    nil,
				arg3:    []byte{0x00},
			},
			wantErrors: []string{
				`BadRequest: failed to decode "raw" request headers for`,
				`procedure "hello" of service "foo" from caller "bar"`,
			},
			wantStatus: tchannel.ErrCodeBadRequest,
		},
		{
			desc: "arg2 parse error",
			sendCall: &fakeInboundCall{
				service: "foo",
				caller:  "bar",
				method:  "hello",
				format:  tchannel.JSON,
				arg2:    []byte("{not valid JSON}"),
				arg3:    []byte{0x00},
			},
			wantErrors: []string{
				`BadRequest: failed to decode "json" request headers for`,
				`procedure "hello" of service "foo" from caller "bar"`,
			},
			wantStatus: tchannel.ErrCodeBadRequest,
		},
		{
			desc: "arg3 reader error",
			sendCall: &fakeInboundCall{
				service: "foo",
				caller:  "bar",
				method:  "hello",
				format:  tchannel.Raw,
				arg2:    []byte{0x00, 0x00},
				arg3:    nil,
			},
			wantErrors: []string{
				`UnexpectedError: error for procedure "hello" of service "foo"`,
			},
			wantStatus: tchannel.ErrCodeUnexpected,
		},
		{
			desc: "internal error",
			sendCall: &fakeInboundCall{
				service: "foo",
				caller:  "bar",
				method:  "hello",
				format:  tchannel.Raw,
				arg2:    []byte{0x00, 0x00},
				arg3:    []byte{0x00},
			},
			expectCall: func(h *transporttest.MockUnaryHandler) {
				h.EXPECT().Handle(
					transporttest.NewContextMatcher(t, transporttest.ContextTTL(time.Second)),
					transporttest.NewRequestMatcher(
						t, &transport.Request{
							Caller:    "bar",
							Service:   "foo",
							Encoding:  raw.Encoding,
							Procedure: "hello",
							Body:      bytes.NewReader([]byte{0x00}),
						},
					), gomock.Any(),
				).Return(fmt.Errorf("great sadness"))
			},
			wantErrors: []string{
				`UnexpectedError: error for procedure "hello" of service "foo":`,
				"great sadness",
			},
			wantStatus: tchannel.ErrCodeUnexpected,
		},
		{
			desc: "arg3 encode error",
			sendCall: &fakeInboundCall{
				service: "foo",
				caller:  "bar",
				method:  "hello",
				format:  tchannel.JSON,
				arg2:    []byte("{}"),
				arg3:    []byte("{}"),
			},
			expectCall: func(h *transporttest.MockUnaryHandler) {
				req := &transport.Request{
					Caller:    "bar",
					Service:   "foo",
					Encoding:  json.Encoding,
					Procedure: "hello",
					Body:      bytes.NewReader([]byte("{}")),
				}
				h.EXPECT().Handle(
					transporttest.NewContextMatcher(t, transporttest.ContextTTL(time.Second)),
					transporttest.NewRequestMatcher(t, req),
					gomock.Any(),
				).Return(
					encoding.ResponseBodyEncodeError(req, errors.New(
						"serialization derp",
					)))
			},
			wantErrors: []string{
				`UnexpectedError: failed to encode "json" response body for`,
				`procedure "hello" of service "foo" from caller "bar":`,
				`serialization derp`,
			},
			wantStatus: tchannel.ErrCodeUnexpected,
		},
		{
			desc: "handler timeout",
			ctxFunc: func() (context.Context, context.CancelFunc) {
				return context.WithTimeout(context.Background(), time.Millisecond)
			},
			sendCall: &fakeInboundCall{
				service: "foo",
				caller:  "bar",
				method:  "waituntiltimeout",
				format:  tchannel.Raw,
				arg2:    []byte{0x00, 0x00},
				arg3:    []byte{0x00},
			},
			expectCall: func(h *transporttest.MockUnaryHandler) {
				req := &transport.Request{
					Service:   "foo",
					Caller:    "bar",
					Procedure: "waituntiltimeout",
					Encoding:  raw.Encoding,
					Body:      bytes.NewReader([]byte{0x00}),
				}
				h.EXPECT().Handle(
					transporttest.NewContextMatcher(
						t, transporttest.ContextTTL(time.Millisecond)),
					transporttest.NewRequestMatcher(t, req),
					gomock.Any(),
				).Do(func(ctx context.Context, _ *transport.Request, _ transport.ResponseWriter) {
					<-ctx.Done()
				}).Return(context.DeadlineExceeded)
			},
			wantErrors: []string{
				`tchannel error ErrCodeTimeout: Timeout: call to procedure "waituntiltimeout" of service "foo" from caller "bar" timed out after `},
			wantStatus: tchannel.ErrCodeTimeout,
		},
		{
			desc: "handler panic",
			sendCall: &fakeInboundCall{
				service: "foo",
				caller:  "bar",
				method:  "panic",
				format:  tchannel.Raw,
				arg2:    []byte{0x00, 0x00},
				arg3:    []byte{0x00},
			},
			expectCall: func(h *transporttest.MockUnaryHandler) {
				req := &transport.Request{
					Service:   "foo",
					Caller:    "bar",
					Procedure: "panic",
					Encoding:  raw.Encoding,
					Body:      bytes.NewReader([]byte{0x00}),
				}
				h.EXPECT().Handle(
					transporttest.NewContextMatcher(
						t, transporttest.ContextTTL(time.Second)),
					transporttest.NewRequestMatcher(t, req),
					gomock.Any(),
				).Do(func(context.Context, *transport.Request, transport.ResponseWriter) {
					panic("oops I panicked!")
				})
			},
			wantErrors: []string{
				`UnexpectedError: error for procedure "panic" of service "foo": panic: oops I panicked!`,
			},
			wantStatus: tchannel.ErrCodeUnexpected,
		},
	}

	for _, tt := range tests {
		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		if tt.ctx != nil {
			ctx = tt.ctx
		} else if tt.ctxFunc != nil {
			ctx, cancel = tt.ctxFunc()
		}
		defer cancel()

		mockCtrl := gomock.NewController(t)
		thandler := transporttest.NewMockUnaryHandler(mockCtrl)
		spec := transport.NewUnaryHandlerSpec(thandler)

		if tt.expectCall != nil {
			tt.expectCall(thandler)
		}

		resp := newResponseRecorder()
		tt.sendCall.resp = resp

		registry := transporttest.NewMockRegistry(mockCtrl)
		registry.EXPECT().GetHandlerSpec(tt.sendCall.service, tt.sendCall.method).
			Return(spec, nil).AnyTimes()

		handler{Registry: registry}.handle(ctx, tt.sendCall)
		err := resp.systemErr
		require.Error(t, err, "expected error for %q", tt.desc)

		systemErr, isSystemErr := err.(tchannel.SystemError)
		require.True(t, isSystemErr, "expected %v for %q to be a system error", err, tt.desc)
		assert.Equal(t, tt.wantStatus, systemErr.Code(), tt.desc)

		for _, msg := range tt.wantErrors {
			assert.Contains(
				t, err.Error(), msg,
				"error should contain message for %q", tt.desc)
		}

		mockCtrl.Finish()
	}
}