blob: 2db5facd825b0af0d5811a3654597df6bc4a5e34 [file]
/*
*
* Copyright 2020 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package test
import (
"context"
"io"
"runtime/pprof"
"sync/atomic"
"testing"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/internal/envconfig"
"google.golang.org/grpc/internal/stubserver"
"google.golang.org/grpc/internal/testutils"
"google.golang.org/grpc/status"
testgrpc "google.golang.org/grpc/interop/grpc_testing"
testpb "google.golang.org/grpc/interop/grpc_testing"
)
type ctxKey string
// TestServerReturningContextError verifies that if a context error is returned
// by the service handler, the status will have the correct status code, not
// Unknown.
func (s) TestServerReturningContextError(t *testing.T) {
ss := &stubserver.StubServer{
EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) {
return nil, context.DeadlineExceeded
},
FullDuplexCallF: func(testgrpc.TestService_FullDuplexCallServer) error {
return context.DeadlineExceeded
},
}
if err := ss.Start(nil); err != nil {
t.Fatalf("Error starting endpoint server: %v", err)
}
defer ss.Stop()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
_, err := ss.Client.EmptyCall(ctx, &testpb.Empty{})
if s, ok := status.FromError(err); !ok || s.Code() != codes.DeadlineExceeded {
t.Fatalf("ss.Client.EmptyCall() got error %v; want <status with Code()=DeadlineExceeded>", err)
}
stream, err := ss.Client.FullDuplexCall(ctx)
if err != nil {
t.Fatalf("unexpected error starting the stream: %v", err)
}
_, err = stream.Recv()
if s, ok := status.FromError(err); !ok || s.Code() != codes.DeadlineExceeded {
t.Fatalf("ss.Client.FullDuplexCall().Recv() got error %v; want <status with Code()=DeadlineExceeded>", err)
}
}
func pprofCtxCollectLabels(ctx context.Context) map[string]string {
seenLabels := map[string]string{}
pprof.ForLabels(ctx, func(k, val string) bool {
seenLabels[k] = val
return true
})
return seenLabels
}
// TestServerSetGoroutineLabelsInContext verifies that when enabled, the
// grpc.method runtime/pprof goroutine label gets set in the context that's
// passed to the handlers.
func (s) TestServerSetGoroutineLabelsInContext(t *testing.T) {
testutils.SetEnvConfig(t, &envconfig.LabelServerGoroutines, envconfig.GoroutineLabelServerMethod)
ss := &stubserver.StubServer{
EmptyCallF: func(ctx context.Context, _ *testpb.Empty) (*testpb.Empty, error) {
ctxLabels := pprofCtxCollectLabels(ctx)
if val, ok := ctxLabels["grpc.method"]; !ok {
t.Errorf("missing \"grpc.method\" label; found labels: %v", ctxLabels)
} else if wantVal := "/grpc.testing.TestService/EmptyCall"; val != wantVal {
t.Errorf("unexpected value for \"grpc.method\" label %q; want %q", ctxLabels["grpc.method"], wantVal)
}
return &testpb.Empty{}, nil
},
FullDuplexCallF: func(stream testgrpc.TestService_FullDuplexCallServer) error {
ctxLabels := pprofCtxCollectLabels(stream.Context())
if val, ok := ctxLabels["grpc.method"]; !ok {
t.Errorf("missing \"grpc.method\" label; found labels: %v", ctxLabels)
} else if wantVal := "/grpc.testing.TestService/FullDuplexCall"; val != wantVal {
t.Errorf("unexpected value for \"grpc.method\" label %q; want %q", ctxLabels["grpc.method"], wantVal)
}
return nil
},
}
if err := ss.Start(nil); err != nil {
t.Fatalf("Error starting endpoint server: %v", err)
}
defer ss.Stop()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if _, err := ss.Client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
t.Fatalf("ss.Client.EmptyCall() got error %v; want OK", err)
}
stream, err := ss.Client.FullDuplexCall(ctx)
if err != nil {
t.Fatalf("unexpected error starting the stream: %v", err)
}
if _, err = stream.Recv(); err != io.EOF {
t.Fatalf("ss.Client.FullDuplexCall().Recv() got error %v; want io.EOF", err)
}
}
// TestServerSetGoroutineLabelsInContextEnvVarDisabled verifies that when
// disable, the grpc.method runtime/pprof goroutine label does _not_ get set in
// the context that's passed to the handlers.
func (s) TestServerSetGoroutineLabelsInContextEnvVarDisabled(t *testing.T) {
testutils.SetEnvConfig(t, &envconfig.LabelServerGoroutines, 0)
ss := &stubserver.StubServer{
EmptyCallF: func(ctx context.Context, _ *testpb.Empty) (*testpb.Empty, error) {
ctxLabels := pprofCtxCollectLabels(ctx)
if val, ok := ctxLabels["grpc.method"]; ok {
t.Errorf("\"grpc.method\" label set with value %q; found labels: %v", val, ctxLabels)
}
return &testpb.Empty{}, nil
},
FullDuplexCallF: func(stream testgrpc.TestService_FullDuplexCallServer) error {
ctxLabels := pprofCtxCollectLabels(stream.Context())
if val, ok := ctxLabels["grpc.method"]; ok {
t.Errorf("\"grpc.method\" label set with value %q; found labels: %v", val, ctxLabels)
}
return nil
},
}
if err := ss.Start(nil); err != nil {
t.Fatalf("Error starting endpoint server: %v", err)
}
defer ss.Stop()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if _, err := ss.Client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
t.Fatalf("ss.Client.EmptyCall() got error %v; want OK", err)
}
stream, err := ss.Client.FullDuplexCall(ctx)
if err != nil {
t.Fatalf("unexpected error starting the stream: %v", err)
}
if _, err = stream.Recv(); err != io.EOF {
t.Fatalf("ss.Client.FullDuplexCall().Recv() got error %v; want io.EOF", err)
}
}
func (s) TestChainUnaryServerInterceptor(t *testing.T) {
var (
firstIntKey = ctxKey("firstIntKey")
secondIntKey = ctxKey("secondIntKey")
)
firstInt := func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
if ctx.Value(firstIntKey) != nil {
return nil, status.Errorf(codes.Internal, "first interceptor should not have %v in context", firstIntKey)
}
if ctx.Value(secondIntKey) != nil {
return nil, status.Errorf(codes.Internal, "first interceptor should not have %v in context", secondIntKey)
}
firstCtx := context.WithValue(ctx, firstIntKey, 0)
resp, err := handler(firstCtx, req)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to handle request at firstInt")
}
simpleResp, ok := resp.(*testpb.SimpleResponse)
if !ok {
return nil, status.Errorf(codes.Internal, "failed to get *testpb.SimpleResponse at firstInt")
}
return &testpb.SimpleResponse{
Payload: &testpb.Payload{
Type: simpleResp.GetPayload().GetType(),
Body: append(simpleResp.GetPayload().GetBody(), '1'),
},
}, nil
}
secondInt := func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
if ctx.Value(firstIntKey) == nil {
return nil, status.Errorf(codes.Internal, "second interceptor should have %v in context", firstIntKey)
}
if ctx.Value(secondIntKey) != nil {
return nil, status.Errorf(codes.Internal, "second interceptor should not have %v in context", secondIntKey)
}
secondCtx := context.WithValue(ctx, secondIntKey, 1)
resp, err := handler(secondCtx, req)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to handle request at secondInt")
}
simpleResp, ok := resp.(*testpb.SimpleResponse)
if !ok {
return nil, status.Errorf(codes.Internal, "failed to get *testpb.SimpleResponse at secondInt")
}
return &testpb.SimpleResponse{
Payload: &testpb.Payload{
Type: simpleResp.GetPayload().GetType(),
Body: append(simpleResp.GetPayload().GetBody(), '2'),
},
}, nil
}
lastInt := func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
if ctx.Value(firstIntKey) == nil {
return nil, status.Errorf(codes.Internal, "last interceptor should have %v in context", firstIntKey)
}
if ctx.Value(secondIntKey) == nil {
return nil, status.Errorf(codes.Internal, "last interceptor should not have %v in context", secondIntKey)
}
resp, err := handler(ctx, req)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to handle request at lastInt at lastInt")
}
simpleResp, ok := resp.(*testpb.SimpleResponse)
if !ok {
return nil, status.Errorf(codes.Internal, "failed to get *testpb.SimpleResponse at lastInt")
}
return &testpb.SimpleResponse{
Payload: &testpb.Payload{
Type: simpleResp.GetPayload().GetType(),
Body: append(simpleResp.GetPayload().GetBody(), '3'),
},
}, nil
}
sopts := []grpc.ServerOption{
grpc.ChainUnaryInterceptor(firstInt, secondInt, lastInt),
}
ss := &stubserver.StubServer{
UnaryCallF: func(context.Context, *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
payload, err := newPayload(testpb.PayloadType_COMPRESSABLE, 0)
if err != nil {
return nil, status.Errorf(codes.Aborted, "failed to make payload: %v", err)
}
return &testpb.SimpleResponse{
Payload: payload,
}, nil
},
}
if err := ss.Start(sopts); err != nil {
t.Fatalf("Error starting endpoint server: %v", err)
}
defer ss.Stop()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
resp, err := ss.Client.UnaryCall(ctx, &testpb.SimpleRequest{})
if s, ok := status.FromError(err); !ok || s.Code() != codes.OK {
t.Fatalf("ss.Client.UnaryCall(ctx, _) = %v, %v; want nil, <status with Code()=OK>", resp, err)
}
respBytes := resp.Payload.GetBody()
if string(respBytes) != "321" {
t.Fatalf("invalid response: want=%s, but got=%s", "321", resp)
}
}
func (s) TestChainOnBaseUnaryServerInterceptor(t *testing.T) {
baseIntKey := ctxKey("baseIntKey")
baseInt := func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
if ctx.Value(baseIntKey) != nil {
return nil, status.Errorf(codes.Internal, "base interceptor should not have %v in context", baseIntKey)
}
baseCtx := context.WithValue(ctx, baseIntKey, 1)
return handler(baseCtx, req)
}
chainInt := func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
if ctx.Value(baseIntKey) == nil {
return nil, status.Errorf(codes.Internal, "chain interceptor should have %v in context", baseIntKey)
}
return handler(ctx, req)
}
sopts := []grpc.ServerOption{
grpc.UnaryInterceptor(baseInt),
grpc.ChainUnaryInterceptor(chainInt),
}
ss := &stubserver.StubServer{
EmptyCallF: func(context.Context, *testpb.Empty) (*testpb.Empty, error) {
return &testpb.Empty{}, nil
},
}
if err := ss.Start(sopts); err != nil {
t.Fatalf("Error starting endpoint server: %v", err)
}
defer ss.Stop()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
resp, err := ss.Client.EmptyCall(ctx, &testpb.Empty{})
if s, ok := status.FromError(err); !ok || s.Code() != codes.OK {
t.Fatalf("ss.Client.EmptyCall(ctx, _) = %v, %v; want nil, <status with Code()=OK>", resp, err)
}
}
func (s) TestChainStreamServerInterceptor(t *testing.T) {
callCounts := make([]atomic.Int32, 4)
firstInt := func(srv any, stream grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if callCounts[0].Load() != 0 {
return status.Errorf(codes.Internal, "callCounts[0] should be 0, but got=%d", callCounts[0].Load())
}
if callCounts[1].Load() != 0 {
return status.Errorf(codes.Internal, "callCounts[1] should be 0, but got=%d", callCounts[1].Load())
}
if callCounts[2].Load() != 0 {
return status.Errorf(codes.Internal, "callCounts[2] should be 0, but got=%d", callCounts[2].Load())
}
if callCounts[3].Load() != 0 {
return status.Errorf(codes.Internal, "callCounts[3] should be 0, but got=%d", callCounts[3].Load())
}
callCounts[0].Add(1)
return handler(srv, stream)
}
secondInt := func(srv any, stream grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if callCounts[0].Load() != 1 {
return status.Errorf(codes.Internal, "callCounts[0] should be 1, but got=%d", callCounts[0].Load())
}
if callCounts[1].Load() != 0 {
return status.Errorf(codes.Internal, "callCounts[1] should be 0, but got=%d", callCounts[1].Load())
}
if callCounts[2].Load() != 0 {
return status.Errorf(codes.Internal, "callCounts[2] should be 0, but got=%d", callCounts[2].Load())
}
if callCounts[3].Load() != 0 {
return status.Errorf(codes.Internal, "callCounts[3] should be 0, but got=%d", callCounts[3].Load())
}
callCounts[1].Add(1)
return handler(srv, stream)
}
lastInt := func(srv any, stream grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if callCounts[0].Load() != 1 {
return status.Errorf(codes.Internal, "callCounts[0] should be 1, but got=%d", callCounts[0].Load())
}
if callCounts[1].Load() != 1 {
return status.Errorf(codes.Internal, "callCounts[1] should be 1, but got=%d", callCounts[1].Load())
}
if callCounts[2].Load() != 0 {
return status.Errorf(codes.Internal, "callCounts[2] should be 0, but got=%d", callCounts[2].Load())
}
if callCounts[3].Load() != 0 {
return status.Errorf(codes.Internal, "callCounts[3] should be 0, but got=%d", callCounts[3].Load())
}
callCounts[2].Add(1)
return handler(srv, stream)
}
sopts := []grpc.ServerOption{
grpc.ChainStreamInterceptor(firstInt, secondInt, lastInt),
}
ss := &stubserver.StubServer{
FullDuplexCallF: func(testgrpc.TestService_FullDuplexCallServer) error {
if callCounts[0].Load() != 1 {
return status.Errorf(codes.Internal, "callCounts[0] should be 1, but got=%d", callCounts[0].Load())
}
if callCounts[1].Load() != 1 {
return status.Errorf(codes.Internal, "callCounts[1] should be 1, but got=%d", callCounts[1].Load())
}
if callCounts[2].Load() != 1 {
return status.Errorf(codes.Internal, "callCounts[2] should be 0, but got=%d", callCounts[2].Load())
}
if callCounts[3].Load() != 0 {
return status.Errorf(codes.Internal, "callCounts[3] should be 0, but got=%d", callCounts[3].Load())
}
callCounts[3].Add(1)
return nil
},
}
if err := ss.Start(sopts); err != nil {
t.Fatalf("Error starting endpoint server: %v", err)
}
defer ss.Stop()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
stream, err := ss.Client.FullDuplexCall(ctx)
if err != nil {
t.Fatalf("failed to FullDuplexCall: %v", err)
}
_, err = stream.Recv()
if err != io.EOF {
t.Fatalf("failed to recv from stream: %v", err)
}
if callCounts[3].Load() != 1 {
t.Fatalf("callCounts[3] should be 1, but got=%d", callCounts[3].Load())
}
}
// Test verifies that only unary interceptors are invoked for unary RPCs and
// only streaming interceptors are invoked for streaming RPCs.
func (s) TestInterceptorSegregation(t *testing.T) {
var unaryCalled, streamCalled atomic.Bool
unaryInt := func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
unaryCalled.Store(true)
return handler(ctx, req)
}
streamInt := func(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
streamCalled.Store(true)
return handler(srv, ss)
}
sopts := []grpc.ServerOption{grpc.UnaryInterceptor(unaryInt), grpc.StreamInterceptor(streamInt)}
ss := &stubserver.StubServer{
UnaryCallF: func(context.Context, *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
return &testpb.SimpleResponse{}, nil
},
FullDuplexCallF: func(testgrpc.TestService_FullDuplexCallServer) error {
return nil
},
}
if err := ss.Start(sopts); err != nil {
t.Fatalf("Error starting endpoint server: %v", err)
}
defer ss.Stop()
// Make a unary RPC and ensure that only the unary interceptor was invoked.
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
defer cancel()
if _, err := ss.Client.UnaryCall(ctx, &testpb.SimpleRequest{}); err != nil {
t.Fatalf("ss.Client.UnaryCall failed: %v", err)
}
if !unaryCalled.Load() {
t.Error("Unary interceptor was not called for Unary RPC")
}
if streamCalled.Load() {
t.Error("Stream interceptor was called for Unary RPC")
}
// Make a streaming RPC and ensure that only the streaming interceptor was
// invoked.
unaryCalled.Store(false)
streamCalled.Store(false)
stream, err := ss.Client.FullDuplexCall(ctx)
if err != nil {
t.Fatalf("ss.Client.FullDuplexCall failed: %v", err)
}
if _, err := stream.Recv(); err != io.EOF {
t.Fatalf("stream.Recv() returned %v, want io.EOF", err)
}
if unaryCalled.Load() {
t.Error("Unary interceptor was called for Streaming RPC")
}
if !streamCalled.Load() {
t.Error("Stream interceptor was not called for Streaming RPC")
}
}