TestUnaryClient_ServerStreamingMismatch ensures that the client's non-streaming RecvMsg() logic correctly handles various error scenarios from the server. The Client initiates a Unary RPC (Invoke), forcing it to use the non-server-streaming `recvMsg` code path (where the bug was). The Server handle
(t *testing.T)
| 79 | // The Server handles it as a Streaming RPC (FullDuplexCall), allowing us to |
| 80 | // send arbitrary sequences of messages and errors. |
| 81 | func (s) TestUnaryClient_ServerStreamingMismatch(t *testing.T) { |
| 82 | tests := []struct { |
| 83 | name string |
| 84 | fullDuplexCallF func(testgrpc.TestService_FullDuplexCallServer) error |
| 85 | wantErrorContains string |
| 86 | wantCode codes.Code |
| 87 | clientCallOptions []grpc.CallOption |
| 88 | }{ |
| 89 | { |
| 90 | name: "server_sends_error_after_message", |
| 91 | fullDuplexCallF: func(stream testgrpc.TestService_FullDuplexCallServer) error { |
| 92 | if err := stream.Send(&testpb.StreamingOutputCallResponse{}); err != nil { |
| 93 | return err |
| 94 | } |
| 95 | return status.Error(codes.Internal, "server error after message") |
| 96 | }, |
| 97 | wantErrorContains: "server error after message", |
| 98 | wantCode: codes.Internal, |
| 99 | }, |
| 100 | { |
| 101 | name: "server_sends_second_message_exceeding_limit", |
| 102 | fullDuplexCallF: func(stream testgrpc.TestService_FullDuplexCallServer) error { |
| 103 | if err := stream.Send(&testpb.StreamingOutputCallResponse{ |
| 104 | Payload: &testpb.Payload{Body: make([]byte, 1)}, |
| 105 | }); err != nil { |
| 106 | return err |
| 107 | } |
| 108 | return stream.Send(&testpb.StreamingOutputCallResponse{ |
| 109 | Payload: &testpb.Payload{Body: make([]byte, 10)}, |
| 110 | }) |
| 111 | }, |
| 112 | clientCallOptions: []grpc.CallOption{grpc.MaxCallRecvMsgSize(5)}, |
| 113 | wantErrorContains: "received message larger than max", |
| 114 | wantCode: codes.ResourceExhausted, |
| 115 | }, |
| 116 | } |
| 117 | |
| 118 | for _, test := range tests { |
| 119 | t.Run(test.name, func(t *testing.T) { |
| 120 | ss := &stubserver.StubServer{ |
| 121 | FullDuplexCallF: test.fullDuplexCallF, |
| 122 | } |
| 123 | if err := ss.Start(nil); err != nil { |
| 124 | t.Fatal("Error starting server:", err) |
| 125 | } |
| 126 | defer ss.Stop() |
| 127 | |
| 128 | ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) |
| 129 | defer cancel() |
| 130 | |
| 131 | // Invoke the streaming RPC method as a Unary RPC. This forces the client |
| 132 | // to use the non-streaming RecvMsg path, while the server handles it as |
| 133 | // a stream (allowing it to send messages and errors in ways a standard |
| 134 | // Unary server cannot). |
| 135 | err := ss.CC.Invoke(ctx, "/grpc.testing.TestService/FullDuplexCall", &testpb.StreamingOutputCallRequest{}, &testpb.StreamingOutputCallResponse{}, test.clientCallOptions...) |
| 136 | if err == nil { |
| 137 | t.Fatal("Client.Invoke returned nil, want error") |
| 138 | } |