go.uber.org/yarpc@v1.72.1/transport/tracer_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 transport_test
    22  
    23  import (
    24  	"context"
    25  	"net"
    26  	"testing"
    27  
    28  	"github.com/opentracing/opentracing-go"
    29  	"github.com/opentracing/opentracing-go/mocktracer"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  	"go.uber.org/yarpc"
    33  	"go.uber.org/yarpc/encoding/json"
    34  	"go.uber.org/yarpc/internal/testtime"
    35  	"go.uber.org/yarpc/internal/yarpctest"
    36  	"go.uber.org/yarpc/transport/grpc"
    37  	"go.uber.org/yarpc/transport/http"
    38  	ytchannel "go.uber.org/yarpc/transport/tchannel"
    39  )
    40  
    41  type echoReqBody struct{}
    42  type echoResBody struct{}
    43  
    44  type handler struct {
    45  	client json.Client
    46  	t      *testing.T
    47  }
    48  
    49  func (h handler) register(dispatcher *yarpc.Dispatcher) {
    50  	dispatcher.Register(json.Procedure("echo", h.handleEcho))
    51  	dispatcher.Register(json.Procedure("echoecho", h.handleEchoEcho))
    52  }
    53  
    54  func (h handler) handleEcho(ctx context.Context, reqBody *echoReqBody) (*echoResBody, error) {
    55  	h.assertBaggage(ctx)
    56  	return &echoResBody{}, nil
    57  }
    58  
    59  func (h handler) handleEchoEcho(ctx context.Context, reqBody *echoReqBody) (*echoResBody, error) {
    60  	h.assertBaggage(ctx)
    61  	var resBody echoResBody
    62  	err := h.client.Call(ctx, "echo", reqBody, &resBody)
    63  	return &resBody, err
    64  }
    65  
    66  func (h handler) echo(ctx context.Context) error {
    67  	return h.client.Call(ctx, "echo", &echoReqBody{}, &echoResBody{})
    68  }
    69  
    70  func (h handler) echoEcho(ctx context.Context) error {
    71  	return h.client.Call(ctx, "echoecho", &echoReqBody{}, &echoResBody{})
    72  }
    73  
    74  func (h handler) createContextWithBaggage(tracer opentracing.Tracer) (context.Context, func()) {
    75  	ctx := context.Background()
    76  	ctx, cancel := context.WithTimeout(ctx, testtime.Second)
    77  
    78  	span := tracer.StartSpan("test")
    79  	// no defer span.Finish()
    80  	span.SetBaggageItem("weapon", "knife")
    81  	ctx = opentracing.ContextWithSpan(ctx, span)
    82  
    83  	return ctx, cancel
    84  }
    85  
    86  func (h handler) assertBaggage(ctx context.Context) {
    87  	span := opentracing.SpanFromContext(ctx)
    88  	weapon := span.BaggageItem("weapon")
    89  	assert.Equal(h.t, "knife", weapon, "baggage should propagate")
    90  }
    91  
    92  func createGRPCDispatcher(t *testing.T, tracer opentracing.Tracer) *yarpc.Dispatcher {
    93  	listener, err := net.Listen("tcp", "127.0.0.1:0")
    94  	require.NoError(t, err)
    95  	grpcTransport := grpc.NewTransport(grpc.Tracer(tracer))
    96  	return yarpc.NewDispatcher(yarpc.Config{
    97  		Name: "yarpc-test",
    98  		Inbounds: yarpc.Inbounds{
    99  			grpcTransport.NewInbound(listener),
   100  		},
   101  		Outbounds: yarpc.Outbounds{
   102  			"yarpc-test": {
   103  				Unary: grpcTransport.NewSingleOutbound(yarpctest.ZeroAddrToHostPort(listener.Addr())),
   104  			},
   105  		},
   106  	})
   107  }
   108  
   109  func createHTTPDispatcher(tracer opentracing.Tracer) *yarpc.Dispatcher {
   110  	// TODO: Use port 0 once https://github.com/yarpc/yarpc-go/issues/381 is
   111  	// fixed.
   112  
   113  	httpTransport := http.NewTransport(http.Tracer(tracer))
   114  	dispatcher := yarpc.NewDispatcher(yarpc.Config{
   115  		Name: "yarpc-test",
   116  		Inbounds: yarpc.Inbounds{
   117  			httpTransport.NewInbound("127.0.0.1:18080"),
   118  		},
   119  		Outbounds: yarpc.Outbounds{
   120  			"yarpc-test": {
   121  				Unary: httpTransport.NewSingleOutbound("http://127.0.0.1:18080"),
   122  			},
   123  		},
   124  	})
   125  
   126  	return dispatcher
   127  }
   128  
   129  //lint:ignore U1000 Ignore "method not used" lint as this is invoked by skipped tests.
   130  func createTChannelDispatcher(t *testing.T, tracer opentracing.Tracer) *yarpc.Dispatcher {
   131  	hp := "127.0.0.1:4040"
   132  
   133  	tchannelTransport, err := ytchannel.NewChannelTransport(
   134  		ytchannel.ListenAddr(hp),
   135  		ytchannel.Tracer(tracer),
   136  		ytchannel.ServiceName("yarpc-test"),
   137  	)
   138  	require.NoError(t, err)
   139  
   140  	dispatcher := yarpc.NewDispatcher(yarpc.Config{
   141  		Name: "yarpc-test",
   142  		Inbounds: yarpc.Inbounds{
   143  			tchannelTransport.NewInbound(),
   144  		},
   145  		Outbounds: yarpc.Outbounds{
   146  			"yarpc-test": {
   147  				Unary: tchannelTransport.NewSingleOutbound(hp),
   148  			},
   149  		},
   150  	})
   151  
   152  	return dispatcher
   153  }
   154  
   155  func TestGRPCTracer(t *testing.T) {
   156  	tracer := mocktracer.New()
   157  	dispatcher := createGRPCDispatcher(t, tracer)
   158  
   159  	client := json.New(dispatcher.ClientConfig("yarpc-test"))
   160  	handler := handler{client: client, t: t}
   161  	handler.register(dispatcher)
   162  
   163  	require.NoError(t, dispatcher.Start())
   164  	defer dispatcher.Stop()
   165  
   166  	ctx, cancel := handler.createContextWithBaggage(tracer)
   167  	defer cancel()
   168  
   169  	err := handler.echo(ctx)
   170  	assert.NoError(t, err)
   171  
   172  	AssertDepth1Spans(t, tracer)
   173  }
   174  
   175  func TestHTTPTracer(t *testing.T) {
   176  	tracer := mocktracer.New()
   177  	dispatcher := createHTTPDispatcher(tracer)
   178  
   179  	client := json.New(dispatcher.ClientConfig("yarpc-test"))
   180  	handler := handler{client: client, t: t}
   181  	handler.register(dispatcher)
   182  
   183  	require.NoError(t, dispatcher.Start())
   184  	defer dispatcher.Stop()
   185  
   186  	ctx, cancel := handler.createContextWithBaggage(tracer)
   187  	defer cancel()
   188  
   189  	err := handler.echo(ctx)
   190  	assert.NoError(t, err)
   191  
   192  	AssertDepth1Spans(t, tracer)
   193  }
   194  
   195  func TestTChannelTracer(t *testing.T) {
   196  	t.Skip("TODO this test is flaky, we need to fix")
   197  	tracer := mocktracer.New()
   198  	dispatcher := createTChannelDispatcher(t, tracer)
   199  	// Make this assertion at the end of the defer stack, when the channel has
   200  	// been shut down. This ensures that all message exchanges have been shut
   201  	// down, which means that all spans have been closed.
   202  	defer AssertDepth1Spans(t, tracer)
   203  
   204  	client := json.New(dispatcher.ClientConfig("yarpc-test"))
   205  	handler := handler{client: client, t: t}
   206  	handler.register(dispatcher)
   207  
   208  	require.NoError(t, dispatcher.Start())
   209  	defer dispatcher.Stop()
   210  
   211  	ctx, cancel := handler.createContextWithBaggage(tracer)
   212  	defer cancel()
   213  
   214  	err := handler.echo(ctx)
   215  	assert.NoError(t, err)
   216  }
   217  
   218  func AssertDepth1Spans(t *testing.T, tracer *mocktracer.MockTracer) {
   219  	assert.Equal(t, 2, len(tracer.FinishedSpans()), "generates inbound and outband spans")
   220  	if len(tracer.FinishedSpans()) != 2 {
   221  		return
   222  	}
   223  	spans := tracer.FinishedSpans()
   224  	parent := spans[0]
   225  	child := spans[1]
   226  	parentctx := parent.Context().(mocktracer.MockSpanContext)
   227  	childctx := child.Context().(mocktracer.MockSpanContext)
   228  	assert.Equal(t, parentctx.TraceID, childctx.TraceID, "parent and child trace ID do not match")
   229  	// Whether the parent and child have the same span id is an implementation
   230  	// detail of the tracer.
   231  	assert.Equal(t, "echo", parent.OperationName, "span has correct operation name")
   232  	assert.Equal(t, "echo", child.OperationName, "span has correct operation name")
   233  }
   234  
   235  func TestGRPCTracerDepth2(t *testing.T) {
   236  	tracer := mocktracer.New()
   237  	dispatcher := createGRPCDispatcher(t, tracer)
   238  
   239  	client := json.New(dispatcher.ClientConfig("yarpc-test"))
   240  	handler := handler{client: client, t: t}
   241  	handler.register(dispatcher)
   242  
   243  	require.NoError(t, dispatcher.Start())
   244  	defer dispatcher.Stop()
   245  
   246  	ctx, cancel := handler.createContextWithBaggage(tracer)
   247  	defer cancel()
   248  
   249  	err := handler.echoEcho(ctx)
   250  	assert.NoError(t, err)
   251  	AssertDepth2Spans(t, tracer)
   252  }
   253  
   254  func TestHTTPTracerDepth2(t *testing.T) {
   255  	tracer := mocktracer.New()
   256  	dispatcher := createHTTPDispatcher(tracer)
   257  
   258  	client := json.New(dispatcher.ClientConfig("yarpc-test"))
   259  	handler := handler{client: client, t: t}
   260  	handler.register(dispatcher)
   261  
   262  	require.NoError(t, dispatcher.Start())
   263  	defer dispatcher.Stop()
   264  
   265  	ctx, cancel := handler.createContextWithBaggage(tracer)
   266  	defer cancel()
   267  
   268  	err := handler.echoEcho(ctx)
   269  	assert.NoError(t, err)
   270  	AssertDepth2Spans(t, tracer)
   271  }
   272  
   273  func TestTChannelTracerDepth2(t *testing.T) {
   274  	t.Skip("TODO this test is flaky, we need to fix")
   275  	tracer := mocktracer.New()
   276  	dispatcher := createTChannelDispatcher(t, tracer)
   277  	// Make this assertion at the end of the defer stack, when the channel has
   278  	// been shut down. This ensures that all message exchanges have been shut
   279  	// down, which means that all spans have been closed.
   280  	defer AssertDepth2Spans(t, tracer)
   281  
   282  	client := json.New(dispatcher.ClientConfig("yarpc-test"))
   283  	handler := handler{client: client, t: t}
   284  	handler.register(dispatcher)
   285  
   286  	require.NoError(t, dispatcher.Start())
   287  	defer dispatcher.Stop()
   288  
   289  	ctx, cancel := handler.createContextWithBaggage(tracer)
   290  	defer cancel()
   291  
   292  	err := handler.echoEcho(ctx)
   293  	assert.NoError(t, err)
   294  }
   295  
   296  func AssertDepth2Spans(t *testing.T, tracer *mocktracer.MockTracer) {
   297  	if !assert.Equal(t, 4, len(tracer.FinishedSpans()), "generates inbound and outband spans") {
   298  		return
   299  	}
   300  	spans := tracer.FinishedSpans()
   301  	ids := mapContexts(spans)
   302  	assert.Equal(t, []int{ids[0], ids[0], ids[0], ids[0]}, ids, "spans share a trace id")
   303  	assert.Equal(t, "echo", spans[0].OperationName, "span has correct operation name")
   304  	assert.Equal(t, "echo", spans[1].OperationName, "span has correct operation name")
   305  	assert.Equal(t, "echoecho", spans[2].OperationName, "span has correct operation name")
   306  	assert.Equal(t, "echoecho", spans[3].OperationName, "span has correct operation name")
   307  }
   308  
   309  func mapContexts(spans []*mocktracer.MockSpan) []int {
   310  	ids := make([]int, len(spans))
   311  	for i, span := range spans {
   312  		ids[i] = span.Context().(mocktracer.MockSpanContext).TraceID
   313  	}
   314  	return ids
   315  }