google.golang.org/grpc@v1.62.1/status/status_ext_test.go (about) 1 /* 2 * 3 * Copyright 2019 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package status_test 20 21 import ( 22 "context" 23 "errors" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/google/go-cmp/cmp" 29 "google.golang.org/grpc" 30 "google.golang.org/grpc/codes" 31 "google.golang.org/grpc/internal/grpctest" 32 "google.golang.org/grpc/internal/stubserver" 33 "google.golang.org/grpc/internal/testutils" 34 "google.golang.org/grpc/metadata" 35 "google.golang.org/grpc/status" 36 "google.golang.org/protobuf/proto" 37 "google.golang.org/protobuf/protoadapt" 38 39 testpb "google.golang.org/grpc/interop/grpc_testing" 40 ) 41 42 const defaultTestTimeout = 10 * time.Second 43 44 type s struct { 45 grpctest.Tester 46 } 47 48 func Test(t *testing.T) { 49 grpctest.RunSubTests(t, s{}) 50 } 51 52 func errWithDetails(t *testing.T, s *status.Status, details ...protoadapt.MessageV1) error { 53 t.Helper() 54 res, err := s.WithDetails(details...) 55 if err != nil { 56 t.Fatalf("(%v).WithDetails(%v) = %v, %v; want _, <nil>", s, details, res, err) 57 } 58 return res.Err() 59 } 60 61 func (s) TestErrorIs(t *testing.T) { 62 // Test errors. 63 testErr := status.Error(codes.Internal, "internal server error") 64 testErrWithDetails := errWithDetails(t, status.New(codes.Internal, "internal server error"), &testpb.Empty{}) 65 66 // Test cases. 67 testCases := []struct { 68 err1, err2 error 69 want bool 70 }{ 71 {err1: testErr, err2: nil, want: false}, 72 {err1: testErr, err2: status.Error(codes.Internal, "internal server error"), want: true}, 73 {err1: testErr, err2: status.Error(codes.Internal, "internal error"), want: false}, 74 {err1: testErr, err2: status.Error(codes.Unknown, "internal server error"), want: false}, 75 {err1: testErr, err2: errors.New("non-grpc error"), want: false}, 76 {err1: testErrWithDetails, err2: status.Error(codes.Internal, "internal server error"), want: false}, 77 {err1: testErrWithDetails, err2: errWithDetails(t, status.New(codes.Internal, "internal server error"), &testpb.Empty{}), want: true}, 78 {err1: testErrWithDetails, err2: errWithDetails(t, status.New(codes.Internal, "internal server error"), &testpb.Empty{}, &testpb.Empty{}), want: false}, 79 } 80 81 for _, tc := range testCases { 82 isError, ok := tc.err1.(interface{ Is(target error) bool }) 83 if !ok { 84 t.Errorf("(%v) does not implement is", tc.err1) 85 continue 86 } 87 88 is := isError.Is(tc.err2) 89 if is != tc.want { 90 t.Errorf("(%v).Is(%v) = %t; want %t", tc.err1, tc.err2, is, tc.want) 91 } 92 } 93 } 94 95 // TestStatusDetails tests how gRPC handles grpc-status-details-bin, especially 96 // in cases where it doesn't match the grpc-status trailer or contains arbitrary 97 // data. 98 func (s) TestStatusDetails(t *testing.T) { 99 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 100 defer cancel() 101 102 for _, serverType := range []struct { 103 name string 104 startServerFunc func(*stubserver.StubServer) error 105 }{{ 106 name: "normal server", 107 startServerFunc: func(ss *stubserver.StubServer) error { 108 return ss.StartServer() 109 }, 110 }, { 111 name: "handler server", 112 startServerFunc: func(ss *stubserver.StubServer) error { 113 return ss.StartHandlerServer() 114 }, 115 }} { 116 t.Run(serverType.name, func(t *testing.T) { 117 // Convenience function for making a status including details. 118 detailErr := func(c codes.Code, m string) error { 119 s, err := status.New(c, m).WithDetails(&testpb.SimpleRequest{ 120 Payload: &testpb.Payload{Body: []byte("detail msg")}, 121 }) 122 if err != nil { 123 t.Fatalf("Error adding details: %v", err) 124 } 125 return s.Err() 126 } 127 128 serialize := func(err error) string { 129 buf, _ := proto.Marshal(status.Convert(err).Proto()) 130 return string(buf) 131 } 132 133 testCases := []struct { 134 name string 135 trailerSent metadata.MD 136 errSent error 137 trailerWant []string 138 errWant error 139 errContains error 140 }{{ 141 name: "basic without details", 142 trailerSent: metadata.MD{}, 143 errSent: status.Error(codes.Aborted, "test msg"), 144 errWant: status.Error(codes.Aborted, "test msg"), 145 }, { 146 name: "basic without details passes through trailers", 147 trailerSent: metadata.MD{"grpc-status-details-bin": []string{"random text"}}, 148 errSent: status.Error(codes.Aborted, "test msg"), 149 trailerWant: []string{"random text"}, 150 errWant: status.Error(codes.Aborted, "test msg"), 151 }, { 152 name: "basic without details conflicts with manual details", 153 trailerSent: metadata.MD{"grpc-status-details-bin": []string{serialize(status.Error(codes.Canceled, "test msg"))}}, 154 errSent: status.Error(codes.Aborted, "test msg"), 155 trailerWant: []string{serialize(status.Error(codes.Canceled, "test msg"))}, 156 errContains: status.Error(codes.Internal, "mismatch"), 157 }, { 158 name: "basic with details", 159 trailerSent: metadata.MD{}, 160 errSent: detailErr(codes.Aborted, "test msg"), 161 trailerWant: []string{serialize(detailErr(codes.Aborted, "test msg"))}, 162 errWant: detailErr(codes.Aborted, "test msg"), 163 }, { 164 name: "basic with details discards user's trailers", 165 trailerSent: metadata.MD{"grpc-status-details-bin": []string{"will be ignored"}}, 166 errSent: detailErr(codes.Aborted, "test msg"), 167 trailerWant: []string{serialize(detailErr(codes.Aborted, "test msg"))}, 168 errWant: detailErr(codes.Aborted, "test msg"), 169 }} 170 171 for _, tc := range testCases { 172 t.Run(tc.name, func(t *testing.T) { 173 // Start a simple server that returns the trailer and error it receives from 174 // channels. 175 ss := &stubserver.StubServer{ 176 UnaryCallF: func(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) { 177 grpc.SetTrailer(ctx, tc.trailerSent) 178 return nil, tc.errSent 179 }, 180 } 181 if err := serverType.startServerFunc(ss); err != nil { 182 t.Fatalf("Error starting endpoint server: %v", err) 183 } 184 if err := ss.StartClient(); err != nil { 185 t.Fatalf("Error starting endpoint client: %v", err) 186 } 187 defer ss.Stop() 188 189 trailerGot := metadata.MD{} 190 _, errGot := ss.Client.UnaryCall(ctx, &testpb.SimpleRequest{}, grpc.Trailer(&trailerGot)) 191 gsdb := trailerGot["grpc-status-details-bin"] 192 if !cmp.Equal(gsdb, tc.trailerWant) { 193 t.Errorf("Trailer got: %v; want: %v", gsdb, tc.trailerWant) 194 } 195 if tc.errWant != nil && !testutils.StatusErrEqual(errGot, tc.errWant) { 196 t.Errorf("Err got: %v; want: %v", errGot, tc.errWant) 197 } 198 if tc.errContains != nil && (status.Code(errGot) != status.Code(tc.errContains) || !strings.Contains(status.Convert(errGot).Message(), status.Convert(tc.errContains).Message())) { 199 t.Errorf("Err got: %v; want: (Contains: %v)", errGot, tc.errWant) 200 } 201 }) 202 } 203 }) 204 } 205 }