Skip to content

Commit 623b3f0

Browse files
test: add regression test for RecvMsg() error shadowing #7510 (#8820)
Fixes: #7510 This PR adds a regression test for a bug where `RecvMsg` could swallow errors in non-server-streaming RPCs (fixed in #7505). Prior to the fix, a variable shadowing error in `csAttempt.recvMsg` caused errors to be ignored in specific scenarios. When `RecvMsg` called `recv` to check for an expected `EOF` after a successful message, any other error returned by `recv` was shadowed by the outer scope's `err` variable, leading `RecvMsg` to return `nil` instead of the actual error. This test verifies that `RecvMsg` correctly propagates these errors instead of swallowing them, preventing future regressions of this logic. RELEASE NOTES: N/A
1 parent 5862457 commit 623b3f0

1 file changed

Lines changed: 78 additions & 0 deletions

File tree

stream_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ package grpc_test
2020

2121
import (
2222
"context"
23+
"strings"
2324
"testing"
2425
"time"
2526

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

0 commit comments

Comments
 (0)