1
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
63 testErr := status.Error(codes.Internal, "internal server error")
64 testErrWithDetails := errWithDetails(t, status.New(codes.Internal, "internal server error"), &testpb.Empty{})
65
66
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
96
97
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
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
174
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 }
206
View as plain text