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 }