google.golang.org/grpc@v1.72.2/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 "reflect" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/google/go-cmp/cmp" 30 "google.golang.org/grpc" 31 "google.golang.org/grpc/codes" 32 "google.golang.org/grpc/internal/grpctest" 33 "google.golang.org/grpc/internal/stubserver" 34 "google.golang.org/grpc/internal/testutils" 35 "google.golang.org/grpc/metadata" 36 "google.golang.org/grpc/status" 37 "google.golang.org/protobuf/proto" 38 "google.golang.org/protobuf/protoadapt" 39 "google.golang.org/protobuf/testing/protocmp" 40 41 testpb "google.golang.org/grpc/interop/grpc_testing" 42 tpb "google.golang.org/grpc/testdata/grpc_testing_not_regenerated" 43 ) 44 45 const defaultTestTimeout = 10 * time.Second 46 47 type s struct { 48 grpctest.Tester 49 } 50 51 func Test(t *testing.T) { 52 grpctest.RunSubTests(t, s{}) 53 } 54 55 func errWithDetails(t *testing.T, s *status.Status, details ...protoadapt.MessageV1) error { 56 t.Helper() 57 res, err := s.WithDetails(details...) 58 if err != nil { 59 t.Fatalf("(%v).WithDetails(%v) = %v, %v; want _, <nil>", s, details, res, err) 60 } 61 return res.Err() 62 } 63 64 func (s) TestErrorIs(t *testing.T) { 65 // Test errors. 66 testErr := status.Error(codes.Internal, "internal server error") 67 testErrWithDetails := errWithDetails(t, status.New(codes.Internal, "internal server error"), &testpb.Empty{}) 68 69 // Test cases. 70 testCases := []struct { 71 err1, err2 error 72 want bool 73 }{ 74 {err1: testErr, err2: nil, want: false}, 75 {err1: testErr, err2: status.Error(codes.Internal, "internal server error"), want: true}, 76 {err1: testErr, err2: status.Error(codes.Internal, "internal error"), want: false}, 77 {err1: testErr, err2: status.Error(codes.Unknown, "internal server error"), want: false}, 78 {err1: testErr, err2: errors.New("non-grpc error"), want: false}, 79 {err1: testErrWithDetails, err2: status.Error(codes.Internal, "internal server error"), want: false}, 80 {err1: testErrWithDetails, err2: errWithDetails(t, status.New(codes.Internal, "internal server error"), &testpb.Empty{}), want: true}, 81 {err1: testErrWithDetails, err2: errWithDetails(t, status.New(codes.Internal, "internal server error"), &testpb.Empty{}, &testpb.Empty{}), want: false}, 82 } 83 84 for _, tc := range testCases { 85 isError, ok := tc.err1.(interface{ Is(target error) bool }) 86 if !ok { 87 t.Errorf("(%v) does not implement is", tc.err1) 88 continue 89 } 90 91 is := isError.Is(tc.err2) 92 if is != tc.want { 93 t.Errorf("(%v).Is(%v) = %t; want %t", tc.err1, tc.err2, is, tc.want) 94 } 95 } 96 } 97 98 // TestStatusDetails tests how gRPC handles grpc-status-details-bin, especially 99 // in cases where it doesn't match the grpc-status trailer or contains arbitrary 100 // data. 101 func (s) TestStatusDetails(t *testing.T) { 102 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 103 defer cancel() 104 105 for _, serverType := range []struct { 106 name string 107 startServerFunc func(*stubserver.StubServer) error 108 }{{ 109 name: "normal server", 110 startServerFunc: func(ss *stubserver.StubServer) error { 111 return ss.StartServer() 112 }, 113 }, { 114 name: "handler server", 115 startServerFunc: func(ss *stubserver.StubServer) error { 116 return ss.StartHandlerServer() 117 }, 118 }} { 119 t.Run(serverType.name, func(t *testing.T) { 120 // Convenience function for making a status including details. 121 detailErr := func(c codes.Code, m string) error { 122 s, err := status.New(c, m).WithDetails(&testpb.SimpleRequest{ 123 Payload: &testpb.Payload{Body: []byte("detail msg")}, 124 }) 125 if err != nil { 126 t.Fatalf("Error adding details: %v", err) 127 } 128 return s.Err() 129 } 130 131 serialize := func(err error) string { 132 buf, _ := proto.Marshal(status.Convert(err).Proto()) 133 return string(buf) 134 } 135 136 testCases := []struct { 137 name string 138 trailerSent metadata.MD 139 errSent error 140 trailerWant []string 141 errWant error 142 errContains error 143 }{{ 144 name: "basic without details", 145 trailerSent: metadata.MD{}, 146 errSent: status.Error(codes.Aborted, "test msg"), 147 errWant: status.Error(codes.Aborted, "test msg"), 148 }, { 149 name: "basic without details passes through trailers", 150 trailerSent: metadata.MD{"grpc-status-details-bin": []string{"random text"}}, 151 errSent: status.Error(codes.Aborted, "test msg"), 152 trailerWant: []string{"random text"}, 153 errWant: status.Error(codes.Aborted, "test msg"), 154 }, { 155 name: "basic without details conflicts with manual details", 156 trailerSent: metadata.MD{"grpc-status-details-bin": []string{serialize(status.Error(codes.Canceled, "test msg"))}}, 157 errSent: status.Error(codes.Aborted, "test msg"), 158 trailerWant: []string{serialize(status.Error(codes.Canceled, "test msg"))}, 159 errContains: status.Error(codes.Internal, "mismatch"), 160 }, { 161 name: "basic with details", 162 trailerSent: metadata.MD{}, 163 errSent: detailErr(codes.Aborted, "test msg"), 164 trailerWant: []string{serialize(detailErr(codes.Aborted, "test msg"))}, 165 errWant: detailErr(codes.Aborted, "test msg"), 166 }, { 167 name: "basic with details discards user's trailers", 168 trailerSent: metadata.MD{"grpc-status-details-bin": []string{"will be ignored"}}, 169 errSent: detailErr(codes.Aborted, "test msg"), 170 trailerWant: []string{serialize(detailErr(codes.Aborted, "test msg"))}, 171 errWant: detailErr(codes.Aborted, "test msg"), 172 }} 173 174 for _, tc := range testCases { 175 t.Run(tc.name, func(t *testing.T) { 176 // Start a simple server that returns the trailer and error it receives from 177 // channels. 178 ss := &stubserver.StubServer{ 179 UnaryCallF: func(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) { 180 grpc.SetTrailer(ctx, tc.trailerSent) 181 return nil, tc.errSent 182 }, 183 } 184 if err := serverType.startServerFunc(ss); err != nil { 185 t.Fatalf("Error starting endpoint server: %v", err) 186 } 187 if err := ss.StartClient(); err != nil { 188 t.Fatalf("Error starting endpoint client: %v", err) 189 } 190 defer ss.Stop() 191 192 trailerGot := metadata.MD{} 193 _, errGot := ss.Client.UnaryCall(ctx, &testpb.SimpleRequest{}, grpc.Trailer(&trailerGot)) 194 gsdb := trailerGot["grpc-status-details-bin"] 195 if !cmp.Equal(gsdb, tc.trailerWant) { 196 t.Errorf("Trailer got: %v; want: %v", gsdb, tc.trailerWant) 197 } 198 if tc.errWant != nil && !testutils.StatusErrEqual(errGot, tc.errWant) { 199 t.Errorf("Err got: %v; want: %v", errGot, tc.errWant) 200 } 201 if tc.errContains != nil && (status.Code(errGot) != status.Code(tc.errContains) || !strings.Contains(status.Convert(errGot).Message(), status.Convert(tc.errContains).Message())) { 202 t.Errorf("Err got: %v; want: (Contains: %v)", errGot, tc.errWant) 203 } 204 }) 205 } 206 }) 207 } 208 } 209 210 // TestStatus_ErrorDetailsMessageV1 verifies backward compatibility of the 211 // status.Details() method when using protobuf code generated with only the 212 // MessageV1 API implementation. 213 func (s) TestStatus_ErrorDetailsMessageV1(t *testing.T) { 214 details := []protoadapt.MessageV1{ 215 &tpb.SimpleMessage{Data: "abc"}, 216 } 217 s, err := status.New(codes.Aborted, "").WithDetails(details...) 218 if err != nil { 219 t.Fatalf("(%v).WithDetails(%+v) failed: %v", s, details, err) 220 } 221 gotDetails := s.Details() 222 for i, msg := range gotDetails { 223 if got, want := reflect.TypeOf(msg), reflect.TypeOf(details[i]); got != want { 224 t.Errorf("reflect.Typeof(%v) = %v, want = %v", msg, got, want) 225 } 226 if _, ok := msg.(protoadapt.MessageV1); !ok { 227 t.Errorf("(%v).Details() returned message that doesn't implement protoadapt.MessageV1: %v", s, msg) 228 } 229 if diff := cmp.Diff(msg, details[i], protocmp.Transform()); diff != "" { 230 t.Errorf("(%v).Details got unexpected output, diff (-got +want):\n%s", s, diff) 231 } 232 } 233 } 234 235 // TestStatus_ErrorDetailsMessageV1AndV2 verifies that status.Details() method 236 // returns the same message types when using protobuf code generated with both the 237 // MessageV1 and MessageV2 API implementations. 238 func (s) TestStatus_ErrorDetailsMessageV1AndV2(t *testing.T) { 239 details := []protoadapt.MessageV1{ 240 &testpb.Empty{}, 241 } 242 s, err := status.New(codes.Aborted, "").WithDetails(details...) 243 if err != nil { 244 t.Fatalf("(%v).WithDetails(%+v) failed: %v", s, details, err) 245 } 246 gotDetails := s.Details() 247 for i, msg := range gotDetails { 248 if got, want := reflect.TypeOf(msg), reflect.TypeOf(details[i]); got != want { 249 t.Errorf("reflect.Typeof(%v) = %v, want = %v", msg, got, want) 250 } 251 if _, ok := msg.(protoadapt.MessageV1); !ok { 252 t.Errorf("(%v).Details() returned message that doesn't implement protoadapt.MessageV1: %v", s, msg) 253 } 254 if _, ok := msg.(protoadapt.MessageV2); !ok { 255 t.Errorf("(%v).Details() returned message that doesn't implement protoadapt.MessageV2: %v", s, msg) 256 } 257 if diff := cmp.Diff(msg, details[i], protocmp.Transform()); diff != "" { 258 t.Errorf("(%v).Details got unexpected output, diff (-got +want):\n%s", s, diff) 259 } 260 } 261 }