go.uber.org/yarpc@v1.72.1/transport/grpc/external_integration_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 grpc_test
    22  
    23  import (
    24  	"context"
    25  	"errors"
    26  	"net"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  	"go.uber.org/yarpc"
    33  	"go.uber.org/yarpc/api/middleware"
    34  	"go.uber.org/yarpc/api/peer"
    35  	"go.uber.org/yarpc/api/transport"
    36  	"go.uber.org/yarpc/internal/prototest/example"
    37  	"go.uber.org/yarpc/internal/prototest/examplepb"
    38  	"go.uber.org/yarpc/peer/hostport"
    39  	"go.uber.org/yarpc/peer/roundrobin"
    40  	"go.uber.org/yarpc/transport/grpc"
    41  	"go.uber.org/yarpc/x/yarpctest"
    42  	"go.uber.org/yarpc/x/yarpctest/api"
    43  	"go.uber.org/yarpc/x/yarpctest/types"
    44  )
    45  
    46  func TestStreamingWithNoCtxDeadline(t *testing.T) {
    47  	// This test ensures that we can use gRPC streaming without a context deadline
    48  	// set. For long-lived streams, it should be unnecesary for users to set a
    49  	// deadline; instead they should use context.WithCancel to cancel the stream.
    50  
    51  	const serviceName = "service-name"
    52  
    53  	// init YARPC transport / inbound / outbound
    54  	grpcTransport := grpc.NewTransport()
    55  	peerList := roundrobin.New(grpcTransport)
    56  
    57  	listener, err := net.Listen("tcp", "127.0.0.1:0")
    58  	require.NoError(t, err, "could not start listener")
    59  	inbound := grpcTransport.NewInbound(listener)
    60  
    61  	dispatcher := yarpc.NewDispatcher(yarpc.Config{
    62  		Name:     serviceName,
    63  		Inbounds: yarpc.Inbounds{inbound},
    64  		Outbounds: yarpc.Outbounds{
    65  			serviceName: {
    66  				ServiceName: serviceName,
    67  				Stream:      grpcTransport.NewOutbound(peerList),
    68  			},
    69  		},
    70  	})
    71  	dispatcher.Register(
    72  		examplepb.BuildFooYARPCProcedures(
    73  			example.NewFooYARPCServer(transport.NewHeaders())))
    74  
    75  	require.NoError(t, dispatcher.Start(), "could not start dispatcher")
    76  	defer func() { assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher") }()
    77  
    78  	// add streaming peer so we can call ourself
    79  	err = peerList.Update(peer.ListUpdates{Additions: []peer.Identifier{
    80  		hostport.PeerIdentifier(listener.Addr().String()),
    81  	}})
    82  	require.NoError(t, err, "could not add peer to peer list")
    83  
    84  	waitForPeerAvailable(t, peerList, time.Second)
    85  
    86  	// init streaming client
    87  	client := examplepb.NewFooYARPCClient(dispatcher.ClientConfig(serviceName))
    88  	ctx, cancel := context.WithCancel(context.Background())
    89  	defer cancel()
    90  
    91  	streamClient, err := client.EchoBoth(ctx)
    92  	require.NoError(t, err, "could not create client stream")
    93  
    94  	// veryify we can send a request
    95  	err = streamClient.Send(&examplepb.EchoBothRequest{
    96  		Message:      "test message!",
    97  		NumResponses: 0,
    98  	})
    99  	require.NoError(t, err, "could not send message")
   100  	assert.NoError(t, streamClient.CloseSend(), "could not close stream")
   101  }
   102  
   103  // waitForPeerAvailable ensures that the peer becomes available before
   104  // proceeding, and that we do not wait forever.
   105  func waitForPeerAvailable(t *testing.T, peerList *roundrobin.List, wait time.Duration) {
   106  	peerAvailable := make(chan struct{})
   107  	go func() {
   108  		for {
   109  			if peerList.Peers()[0].Status().ConnectionStatus == peer.Available {
   110  				close(peerAvailable)
   111  				return
   112  			}
   113  			time.Sleep(10 * time.Millisecond)
   114  		}
   115  	}()
   116  
   117  	select {
   118  	case <-time.After(wait):
   119  		t.Fatal("failed waiting to connect to peer")
   120  	case <-peerAvailable:
   121  		return
   122  	}
   123  }
   124  
   125  func TestFoo(t *testing.T) {
   126  	const (
   127  		serviceName   = "test-service"
   128  		procedureName = "test-procedure"
   129  
   130  		appErrName    = "ProtoAppErrName"
   131  		appErrDetails = " this is an app error detail string!"
   132  
   133  		portName = "port"
   134  	)
   135  
   136  	handler := &types.UnaryHandler{
   137  		Handler: api.UnaryHandlerFunc(func(ctx context.Context, req *transport.Request, resw transport.ResponseWriter) error {
   138  			// simulate Protobuf encoding setting `transport.ApplicationErrorMeta`
   139  			metaSetter, ok := resw.(transport.ApplicationErrorMetaSetter)
   140  			if !ok {
   141  				return errors.New("missing transport.ApplicationErrorMetaSetter")
   142  			}
   143  			metaSetter.SetApplicationErrorMeta(&transport.ApplicationErrorMeta{
   144  				Name:    appErrName,
   145  				Details: appErrDetails,
   146  			})
   147  			return nil
   148  		})}
   149  
   150  	outboundMwAssertion := middleware.UnaryOutboundFunc(
   151  		func(ctx context.Context, req *transport.Request, next transport.UnaryOutbound) (*transport.Response, error) {
   152  			res, err := next.Call(ctx, req)
   153  
   154  			// verify gRPC propagating `transport.ApplicationErrorMeta`
   155  			require.NotNil(t, res.ApplicationErrorMeta, "missing transport.ApplicationErrorMeta")
   156  			assert.Equal(t, appErrName, res.ApplicationErrorMeta.Name, "incorrect app error name")
   157  			assert.Equal(t, appErrDetails, res.ApplicationErrorMeta.Details, "incorrect app error message")
   158  			assert.Nil(t, res.ApplicationErrorMeta.Code, "unexpected code")
   159  
   160  			return res, err
   161  		})
   162  
   163  	portProvider := yarpctest.NewPortProvider(t)
   164  	service := yarpctest.GRPCService(
   165  		yarpctest.Name(serviceName),
   166  		portProvider.NamedPort(portName),
   167  		yarpctest.Proc(yarpctest.Name(procedureName), handler),
   168  	)
   169  	require.NoError(t, service.Start(t))
   170  	defer func() { assert.NoError(t, service.Stop(t)) }()
   171  
   172  	request := yarpctest.GRPCRequest(
   173  		yarpctest.Service(serviceName),
   174  		portProvider.NamedPort(portName),
   175  		yarpctest.Procedure(procedureName),
   176  		yarpctest.GiveTimeout(time.Second),
   177  		api.RequestOptionFunc(func(opts *api.RequestOpts) {
   178  			opts.UnaryMiddleware = []middleware.UnaryOutbound{outboundMwAssertion}
   179  		}),
   180  	)
   181  	request.Run(t)
   182  }