snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Has started running
govulncheck / govulncheck (./gcp/observability) (push) Waiting to run
govulncheck / govulncheck (./security/advancedtls) (push) Waiting to run
govulncheck / govulncheck (./stats/opencensus) (push) Waiting to run
govulncheck / govulncheck (.) (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Has started running
govulncheck / govulncheck (./gcp/observability) (push) Waiting to run
govulncheck / govulncheck (./security/advancedtls) (push) Waiting to run
govulncheck / govulncheck (./stats/opencensus) (push) Waiting to run
govulncheck / govulncheck (.) (push) Failing after 1s
This commit is contained in:
281
encoding/compressor_test.go
Normal file
281
encoding/compressor_test.go
Normal file
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2025 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package encoding_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/encoding"
|
||||
"google.golang.org/grpc/encoding/internal"
|
||||
"google.golang.org/grpc/internal/stubserver"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
testgrpc "google.golang.org/grpc/interop/grpc_testing"
|
||||
testpb "google.golang.org/grpc/interop/grpc_testing"
|
||||
|
||||
_ "google.golang.org/grpc/encoding/gzip"
|
||||
)
|
||||
|
||||
// wrapCompressor is a wrapper of encoding.Compressor which maintains count of
|
||||
// Compressor method invokes.
|
||||
type wrapCompressor struct {
|
||||
encoding.Compressor
|
||||
compressInvokes int32
|
||||
}
|
||||
|
||||
func (wc *wrapCompressor) Compress(w io.Writer) (io.WriteCloser, error) {
|
||||
atomic.AddInt32(&wc.compressInvokes, 1)
|
||||
return wc.Compressor.Compress(w)
|
||||
}
|
||||
|
||||
func setupGzipWrapCompressor(t *testing.T) *wrapCompressor {
|
||||
regFn := internal.RegisterCompressorForTesting.(func(encoding.Compressor) func())
|
||||
c := &wrapCompressor{Compressor: encoding.GetCompressor("gzip")}
|
||||
unreg := regFn(c)
|
||||
t.Cleanup(unreg)
|
||||
return c
|
||||
}
|
||||
|
||||
func (s) TestSetSendCompressorSuccess(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
desc string
|
||||
payload *testpb.Payload
|
||||
dialOpts []grpc.DialOption
|
||||
resCompressor string
|
||||
wantCompressInvokes int32
|
||||
}{
|
||||
{
|
||||
name: "identity_request_and_gzip_response",
|
||||
desc: "request is uncompressed and response is gzip compressed",
|
||||
payload: &testpb.Payload{Body: []byte("payload")},
|
||||
resCompressor: "gzip",
|
||||
wantCompressInvokes: 1,
|
||||
},
|
||||
{
|
||||
name: "identity_request_and_empty_response",
|
||||
desc: "request is uncompressed and response is gzip compressed",
|
||||
payload: nil,
|
||||
resCompressor: "gzip",
|
||||
wantCompressInvokes: 0,
|
||||
},
|
||||
{
|
||||
name: "gzip_request_and_identity_response",
|
||||
desc: "request is gzip compressed and response is uncompressed with identity",
|
||||
payload: &testpb.Payload{Body: []byte("payload")},
|
||||
resCompressor: "identity",
|
||||
dialOpts: []grpc.DialOption{
|
||||
// Use WithCompressor instead of UseCompressor to avoid counting
|
||||
// the client's compressor usage.
|
||||
grpc.WithCompressor(grpc.NewGZIPCompressor()),
|
||||
},
|
||||
wantCompressInvokes: 0,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Run("unary", func(t *testing.T) {
|
||||
testUnarySetSendCompressorSuccess(t, tt.payload, tt.resCompressor, tt.wantCompressInvokes, tt.dialOpts)
|
||||
})
|
||||
|
||||
t.Run("stream", func(t *testing.T) {
|
||||
testStreamSetSendCompressorSuccess(t, tt.payload, tt.resCompressor, tt.wantCompressInvokes, tt.dialOpts)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testUnarySetSendCompressorSuccess(t *testing.T, payload *testpb.Payload, resCompressor string, wantCompressInvokes int32, dialOpts []grpc.DialOption) {
|
||||
wc := setupGzipWrapCompressor(t)
|
||||
ss := &stubserver.StubServer{
|
||||
UnaryCallF: func(ctx context.Context, _ *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
|
||||
if err := grpc.SetSendCompressor(ctx, resCompressor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &testpb.SimpleResponse{
|
||||
Payload: payload,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
if err := ss.Start(nil, dialOpts...); err != nil {
|
||||
t.Fatalf("Error starting endpoint server: %v", err)
|
||||
}
|
||||
defer ss.Stop()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
if _, err := ss.Client.UnaryCall(ctx, &testpb.SimpleRequest{}); err != nil {
|
||||
t.Fatalf("Unexpected unary call error, got: %v, want: nil", err)
|
||||
}
|
||||
|
||||
compressInvokes := atomic.LoadInt32(&wc.compressInvokes)
|
||||
if compressInvokes != wantCompressInvokes {
|
||||
t.Fatalf("Unexpected compress invokes, got:%d, want: %d", compressInvokes, wantCompressInvokes)
|
||||
}
|
||||
}
|
||||
|
||||
func testStreamSetSendCompressorSuccess(t *testing.T, payload *testpb.Payload, resCompressor string, wantCompressInvokes int32, dialOpts []grpc.DialOption) {
|
||||
wc := setupGzipWrapCompressor(t)
|
||||
ss := &stubserver.StubServer{
|
||||
FullDuplexCallF: func(stream testgrpc.TestService_FullDuplexCallServer) error {
|
||||
if _, err := stream.Recv(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := grpc.SetSendCompressor(stream.Context(), resCompressor); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return stream.Send(&testpb.StreamingOutputCallResponse{
|
||||
Payload: payload,
|
||||
})
|
||||
},
|
||||
}
|
||||
if err := ss.Start(nil, dialOpts...); err != nil {
|
||||
t.Fatalf("Error starting endpoint server: %v", err)
|
||||
}
|
||||
defer ss.Stop()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
s, err := ss.Client.FullDuplexCall(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected full duplex call error, got: %v, want: nil", err)
|
||||
}
|
||||
|
||||
if err := s.Send(&testpb.StreamingOutputCallRequest{}); err != nil {
|
||||
t.Fatalf("Unexpected full duplex call send error, got: %v, want: nil", err)
|
||||
}
|
||||
|
||||
if _, err := s.Recv(); err != nil {
|
||||
t.Fatalf("Unexpected full duplex recv error, got: %v, want: nil", err)
|
||||
}
|
||||
|
||||
compressInvokes := atomic.LoadInt32(&wc.compressInvokes)
|
||||
if compressInvokes != wantCompressInvokes {
|
||||
t.Fatalf("Unexpected compress invokes, got:%d, want: %d", compressInvokes, wantCompressInvokes)
|
||||
}
|
||||
}
|
||||
|
||||
// fakeCompressor returns a messages of a configured size, irrespective of the
|
||||
// input.
|
||||
type fakeCompressor struct {
|
||||
decompressedMessageSize int
|
||||
}
|
||||
|
||||
func (f *fakeCompressor) Compress(w io.Writer) (io.WriteCloser, error) {
|
||||
return nopWriteCloser{w}, nil
|
||||
}
|
||||
|
||||
func (f *fakeCompressor) Decompress(io.Reader) (io.Reader, error) {
|
||||
return bytes.NewReader(make([]byte, f.decompressedMessageSize)), nil
|
||||
}
|
||||
|
||||
func (f *fakeCompressor) Name() string {
|
||||
// Use the name of an existing compressor to avoid interactions with other
|
||||
// tests since compressors can't be un-registered.
|
||||
return "fake"
|
||||
}
|
||||
|
||||
type nopWriteCloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (nopWriteCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestDecompressionExceedsMaxMessageSize uses a fake compressor that produces
|
||||
// messages of size 100 bytes on decompression. A server is started with the
|
||||
// max receive message size restricted to 99 bytes. The test verifies that the
|
||||
// client receives a ResourceExhausted response from the server.
|
||||
func (s) TestDecompressionExceedsMaxMessageSize(t *testing.T) {
|
||||
const messageLen = 100
|
||||
regFn := internal.RegisterCompressorForTesting.(func(encoding.Compressor) func())
|
||||
compressor := &fakeCompressor{decompressedMessageSize: messageLen}
|
||||
unreg := regFn(compressor)
|
||||
defer unreg()
|
||||
ss := &stubserver.StubServer{
|
||||
UnaryCallF: func(context.Context, *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
|
||||
return &testpb.SimpleResponse{}, nil
|
||||
},
|
||||
}
|
||||
if err := ss.Start([]grpc.ServerOption{grpc.MaxRecvMsgSize(messageLen - 1)}); err != nil {
|
||||
t.Fatalf("Error starting endpoint server: %v", err)
|
||||
}
|
||||
defer ss.Stop()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
req := &testpb.SimpleRequest{Payload: &testpb.Payload{}}
|
||||
_, err := ss.Client.UnaryCall(ctx, req, grpc.UseCompressor(compressor.Name()))
|
||||
if got, want := status.Code(err), codes.ResourceExhausted; got != want {
|
||||
t.Errorf("Client.UnaryCall(%+v) returned status %v, want %v", req, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Test verifies that decompression failures (after stream establishment) are
|
||||
// handled gracefully without duplicate status writes.
|
||||
func (s) TestStreamingDecompressionExceedsMaxMessageSize(t *testing.T) {
|
||||
const messageLen = 100
|
||||
regFn := internal.RegisterCompressorForTesting.(func(encoding.Compressor) func())
|
||||
compressor := &fakeCompressor{decompressedMessageSize: messageLen}
|
||||
unreg := regFn(compressor)
|
||||
defer unreg()
|
||||
|
||||
ss := &stubserver.StubServer{
|
||||
FullDuplexCallF: func(stream testgrpc.TestService_FullDuplexCallServer) error {
|
||||
for {
|
||||
if _, err := stream.Recv(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
if err := ss.Start([]grpc.ServerOption{grpc.MaxRecvMsgSize(messageLen - 1)}); err != nil {
|
||||
t.Fatalf("Error starting endpoint server: %v", err)
|
||||
}
|
||||
defer ss.Stop()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
|
||||
defer cancel()
|
||||
stream, err := ss.Client.FullDuplexCall(ctx, grpc.UseCompressor(compressor.Name()))
|
||||
if err != nil {
|
||||
t.Fatalf("FullDuplexCall failed: %v", err)
|
||||
}
|
||||
|
||||
// Send message that will result in decompression failure on the server
|
||||
// because it exceeds the max message size.
|
||||
if err := stream.Send(&testpb.StreamingOutputCallRequest{Payload: &testpb.Payload{Body: []byte("payload")}}); err != nil {
|
||||
t.Fatalf("Send() failed: %v", err)
|
||||
}
|
||||
|
||||
// We expect the stream to fail with ResourceExhausted on Recv.
|
||||
if _, err = stream.Recv(); status.Code(err) != codes.ResourceExhausted {
|
||||
t.Fatalf("stream.Recv() returned status %v, want %v", status.Code(err), codes.ResourceExhausted)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user