go.uber.org/yarpc@v1.72.1/encoding/protobuf/stream_test.go (about)

     1  // Copyright (c) 2022 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package protobuf_test
    22  
    23  import (
    24  	"context"
    25  	"errors"
    26  	"io"
    27  	"testing"
    28  
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/require"
    31  	"go.uber.org/yarpc"
    32  	"go.uber.org/yarpc/api/transport"
    33  	"go.uber.org/yarpc/encoding/protobuf"
    34  	"go.uber.org/yarpc/encoding/protobuf/internal/testpb"
    35  	"go.uber.org/yarpc/internal/clientconfig"
    36  	"go.uber.org/yarpc/peer"
    37  	"go.uber.org/yarpc/peer/hostport"
    38  	"go.uber.org/yarpc/yarpctest"
    39  )
    40  
    41  var protocolOptionsTable = []struct {
    42  	msg  string
    43  	opts []protobuf.ClientOption
    44  }{
    45  	{
    46  		msg: "protobuf",
    47  	},
    48  	{
    49  		msg: "json",
    50  		opts: []protobuf.ClientOption{
    51  			protobuf.UseJSON,
    52  		},
    53  	},
    54  }
    55  
    56  func TestUnary(t *testing.T) {
    57  	for _, tt := range protocolOptionsTable {
    58  		t.Run(tt.msg, func(t *testing.T) {
    59  			server := &testServer{}
    60  			procedures := testpb.BuildTestYARPCProcedures(server)
    61  			router := yarpc.NewMapRouter("test")
    62  			router.Register(procedures)
    63  
    64  			trans := yarpctest.NewFakeTransport()
    65  			pc := peer.NewSingle(hostport.Identify("1"), trans)
    66  			ob := trans.NewOutbound(pc, yarpctest.OutboundRouter(router))
    67  			cc := clientconfig.MultiOutbound("test", "test", transport.Outbounds{
    68  				Unary: ob,
    69  			})
    70  			client := testpb.NewTestYARPCClient(cc, tt.opts...)
    71  
    72  			ctx, cancel := context.WithCancel(context.Background())
    73  			defer cancel()
    74  
    75  			sent := &testpb.TestMessage{Value: "echo"}
    76  			received, err := client.Unary(ctx, sent)
    77  			require.NoError(t, err)
    78  			assert.Equal(t, sent, received)
    79  		})
    80  	}
    81  }
    82  
    83  var _ testpb.TestYARPCServer = (*testServer)(nil)
    84  
    85  // testServer provides unary and streaming echo method implementations.
    86  type testServer struct{}
    87  
    88  func (s *testServer) Unary(ctx context.Context, msg *testpb.TestMessage) (*testpb.TestMessage, error) {
    89  	return msg, nil
    90  }
    91  
    92  func (s *testServer) Duplex(str testpb.TestServiceDuplexYARPCServer) error {
    93  	for {
    94  		msg, err := str.Recv()
    95  		if err != nil {
    96  			return err
    97  		}
    98  		if msg.Value == "please explode" {
    99  			return errors.New("explosion occurred")
   100  		}
   101  		err = str.Send(msg)
   102  		if err != nil {
   103  			return err
   104  		}
   105  	}
   106  }
   107  
   108  func TestDuplexStream(t *testing.T) {
   109  	for _, tt := range protocolOptionsTable {
   110  		t.Run(tt.msg, func(t *testing.T) {
   111  			server := &testServer{}
   112  			procedures := testpb.BuildTestYARPCProcedures(server)
   113  			router := yarpc.NewMapRouter("test")
   114  			router.Register(procedures)
   115  
   116  			trans := yarpctest.NewFakeTransport()
   117  			pc := peer.NewSingle(hostport.Identify("1"), trans)
   118  			ob := trans.NewOutbound(pc, yarpctest.OutboundRouter(router))
   119  			cc := clientconfig.MultiOutbound("test", "test", transport.Outbounds{
   120  				Stream: ob,
   121  			})
   122  			client := testpb.NewTestYARPCClient(cc, tt.opts...)
   123  
   124  			ctx, cancel := context.WithCancel(context.Background())
   125  			defer cancel()
   126  
   127  			str, err := client.Duplex(ctx)
   128  			require.NoError(t, err)
   129  
   130  			// Send a message.
   131  			sent := &testpb.TestMessage{Value: "echo"}
   132  			{
   133  				err := str.Send(sent)
   134  				require.NoError(t, err)
   135  			}
   136  
   137  			// Receive the echoed message.
   138  			{
   139  				msg, err := str.Recv()
   140  				require.NoError(t, err)
   141  				assert.Equal(t, sent, msg)
   142  			}
   143  
   144  			// Close the client side of the stream.
   145  			str.CloseSend()
   146  
   147  			// Verify that the server closes as well.
   148  			{
   149  				_, err := str.Recv()
   150  				require.Equal(t, err, io.EOF)
   151  			}
   152  		})
   153  	}
   154  }
   155  
   156  func TestStreamServerError(t *testing.T) {
   157  	table := []struct {
   158  		msg          string
   159  		clientCloses bool
   160  	}{
   161  		{
   162  			msg:          "client closes after error",
   163  			clientCloses: true,
   164  		},
   165  		{
   166  			msg:          "client returns immediately after error",
   167  			clientCloses: false,
   168  		},
   169  	}
   170  
   171  	for _, ct := range table {
   172  		t.Run(ct.msg, func(t *testing.T) {
   173  			for _, tt := range protocolOptionsTable {
   174  				t.Run(tt.msg, func(t *testing.T) {
   175  					server := &testServer{}
   176  					procedures := testpb.BuildTestYARPCProcedures(server)
   177  					router := yarpc.NewMapRouter("test")
   178  					router.Register(procedures)
   179  
   180  					trans := yarpctest.NewFakeTransport()
   181  					pc := peer.NewSingle(hostport.Identify("1"), trans)
   182  					ob := trans.NewOutbound(pc, yarpctest.OutboundRouter(router))
   183  					cc := clientconfig.MultiOutbound("test", "test", transport.Outbounds{
   184  						Stream: ob,
   185  					})
   186  					client := testpb.NewTestYARPCClient(cc, tt.opts...)
   187  
   188  					ctx, cancel := context.WithCancel(context.Background())
   189  					defer cancel()
   190  
   191  					str, err := client.Duplex(ctx)
   192  					require.NoError(t, err)
   193  
   194  					// Send a message.
   195  					sent := &testpb.TestMessage{Value: "please explode"}
   196  					{
   197  						err := str.Send(sent)
   198  						require.NoError(t, err)
   199  					}
   200  
   201  					// Receive the handler error on next receive.
   202  					{
   203  						msg, err := str.Recv()
   204  						require.Error(t, err)
   205  						assert.Nil(t, msg)
   206  					}
   207  
   208  					// Close the client side of the stream.
   209  					if ct.clientCloses {
   210  						str.CloseSend()
   211  					}
   212  
   213  					// Verify that the server closes as well.
   214  					{
   215  						msg, err := str.Recv()
   216  						require.Equal(t, err, io.EOF)
   217  						assert.Nil(t, msg)
   218  					}
   219  				})
   220  			}
   221  		})
   222  	}
   223  }