go.uber.org/yarpc@v1.72.1/encoding/protobuf/v2/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 v2_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/internal/testpb/v2"
    34  	"go.uber.org/yarpc/encoding/protobuf/v2"
    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  	"google.golang.org/protobuf/proto"
    40  )
    41  
    42  var protocolOptionsTable = []struct {
    43  	msg  string
    44  	opts []v2.ClientOption
    45  }{
    46  	{
    47  		msg: "protobuf",
    48  	},
    49  	{
    50  		msg: "json",
    51  		opts: []v2.ClientOption{
    52  			v2.UseJSON,
    53  		},
    54  	},
    55  }
    56  
    57  func TestUnary(t *testing.T) {
    58  	for _, tt := range protocolOptionsTable {
    59  		t.Run(tt.msg, func(t *testing.T) {
    60  			server := &testServer{}
    61  			procedures := testpb.BuildTestYARPCProcedures(server)
    62  			router := yarpc.NewMapRouter("test")
    63  			router.Register(procedures)
    64  
    65  			trans := yarpctest.NewFakeTransport()
    66  			pc := peer.NewSingle(hostport.Identify("1"), trans)
    67  			ob := trans.NewOutbound(pc, yarpctest.OutboundRouter(router))
    68  			cc := clientconfig.MultiOutbound("test", "test", transport.Outbounds{
    69  				Unary: ob,
    70  			})
    71  			client := testpb.NewTestYARPCClient(cc, tt.opts...)
    72  
    73  			ctx, cancel := context.WithCancel(context.Background())
    74  			defer cancel()
    75  
    76  			sent := &testpb.TestMessage{Value: "echo"}
    77  			received, err := client.Unary(ctx, sent)
    78  			require.NoError(t, err)
    79  			assert.True(t, proto.Equal(sent, received))
    80  		})
    81  	}
    82  }
    83  
    84  var _ testpb.TestYARPCServer = (*testServer)(nil)
    85  
    86  // testServer provides unary and streaming echo method implementations.
    87  type testServer struct{}
    88  
    89  func (s *testServer) Unary(ctx context.Context, msg *testpb.TestMessage) (*testpb.TestMessage, error) {
    90  	return msg, nil
    91  }
    92  
    93  func (s *testServer) Duplex(str testpb.TestServiceDuplexYARPCServer) error {
    94  	for {
    95  		msg, err := str.Recv()
    96  		if err != nil {
    97  			return err
    98  		}
    99  		if msg.Value == "please explode" {
   100  			return errors.New("explosion occurred")
   101  		}
   102  		err = str.Send(msg)
   103  		if err != nil {
   104  			return err
   105  		}
   106  	}
   107  }
   108  
   109  func TestDuplexStream(t *testing.T) {
   110  	for _, tt := range protocolOptionsTable {
   111  		t.Run(tt.msg, func(t *testing.T) {
   112  			server := &testServer{}
   113  			procedures := testpb.BuildTestYARPCProcedures(server)
   114  			router := yarpc.NewMapRouter("test")
   115  			router.Register(procedures)
   116  
   117  			trans := yarpctest.NewFakeTransport()
   118  			pc := peer.NewSingle(hostport.Identify("1"), trans)
   119  			ob := trans.NewOutbound(pc, yarpctest.OutboundRouter(router))
   120  			cc := clientconfig.MultiOutbound("test", "test", transport.Outbounds{
   121  				Stream: ob,
   122  			})
   123  			client := testpb.NewTestYARPCClient(cc, tt.opts...)
   124  
   125  			ctx, cancel := context.WithCancel(context.Background())
   126  			defer cancel()
   127  
   128  			str, err := client.Duplex(ctx)
   129  			require.NoError(t, err)
   130  
   131  			// Send a message.
   132  			sent := &testpb.TestMessage{Value: "echo"}
   133  			{
   134  				err := str.Send(sent)
   135  				require.NoError(t, err)
   136  			}
   137  
   138  			// Receive the echoed message.
   139  			{
   140  				msg, err := str.Recv()
   141  				require.NoError(t, err)
   142  				assert.True(t, proto.Equal(sent, msg))
   143  			}
   144  
   145  			// Close the client side of the stream.
   146  			str.CloseSend()
   147  
   148  			// Verify that the server closes as well.
   149  			{
   150  				_, err := str.Recv()
   151  				require.Equal(t, err, io.EOF)
   152  			}
   153  		})
   154  	}
   155  }
   156  
   157  func TestStreamServerError(t *testing.T) {
   158  	table := []struct {
   159  		msg          string
   160  		clientCloses bool
   161  	}{
   162  		{
   163  			msg:          "client closes after error",
   164  			clientCloses: true,
   165  		},
   166  		{
   167  			msg:          "client returns immediately after error",
   168  			clientCloses: false,
   169  		},
   170  	}
   171  
   172  	for _, ct := range table {
   173  		t.Run(ct.msg, func(t *testing.T) {
   174  			for _, tt := range protocolOptionsTable {
   175  				t.Run(tt.msg, func(t *testing.T) {
   176  					server := &testServer{}
   177  					procedures := testpb.BuildTestYARPCProcedures(server)
   178  					router := yarpc.NewMapRouter("test")
   179  					router.Register(procedures)
   180  
   181  					trans := yarpctest.NewFakeTransport()
   182  					pc := peer.NewSingle(hostport.Identify("1"), trans)
   183  					ob := trans.NewOutbound(pc, yarpctest.OutboundRouter(router))
   184  					cc := clientconfig.MultiOutbound("test", "test", transport.Outbounds{
   185  						Stream: ob,
   186  					})
   187  					client := testpb.NewTestYARPCClient(cc, tt.opts...)
   188  
   189  					ctx, cancel := context.WithCancel(context.Background())
   190  					defer cancel()
   191  
   192  					str, err := client.Duplex(ctx)
   193  					require.NoError(t, err)
   194  
   195  					// Send a message.
   196  					sent := &testpb.TestMessage{Value: "please explode"}
   197  					{
   198  						err := str.Send(sent)
   199  						require.NoError(t, err)
   200  					}
   201  
   202  					// Receive the handler error on next receive.
   203  					{
   204  						msg, err := str.Recv()
   205  						require.Error(t, err)
   206  						assert.Nil(t, msg)
   207  					}
   208  
   209  					// Close the client side of the stream.
   210  					if ct.clientCloses {
   211  						str.CloseSend()
   212  					}
   213  
   214  					// Verify that the server closes as well.
   215  					{
   216  						msg, err := str.Recv()
   217  						require.Equal(t, err, io.EOF)
   218  						assert.Nil(t, msg)
   219  					}
   220  				})
   221  			}
   222  		})
   223  	}
   224  }