@@ -212,3 +212,178 @@ where
212212 Either :: Left ( self . inner . call ( req) )
213213 }
214214}
215+
216+ #[ cfg( test) ]
217+ mod tests {
218+ use super :: * ;
219+ use crate :: Id ;
220+ use bytes:: Bytes ;
221+ use http:: HeaderMap ;
222+ use linkerd_error:: Error ;
223+ use linkerd_http_box:: BoxBody ;
224+ use std:: collections:: BTreeMap ;
225+ use tokio:: sync:: mpsc;
226+ use tower:: { Layer , Service , ServiceExt } ;
227+
228+ const W3C_TRACEPARENT_HEADER : & str = "traceparent" ;
229+ const B3_TRACE_ID_HEADER : & str = "x-b3-traceid" ;
230+ const B3_SPAN_ID_HEADER : & str = "x-b3-spanid" ;
231+ const B3_SAMPLED_HEADER : & str = "x-b3-sampled" ;
232+
233+ #[ tokio:: test( flavor = "current_thread" ) ]
234+ async fn w3c_propagation ( ) {
235+ let _trace = linkerd_tracing:: test:: trace_init ( ) ;
236+
237+ let ( req_headers, exported_span) = send_mock_request (
238+ http:: Request :: builder ( )
239+ . header (
240+ W3C_TRACEPARENT_HEADER ,
241+ "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" ,
242+ )
243+ . body ( BoxBody :: empty ( ) )
244+ . expect ( "request" ) ,
245+ )
246+ . await ;
247+
248+ assert ! ( req_headers. get( W3C_TRACEPARENT_HEADER ) . is_some( ) ) ;
249+ assert ! ( req_headers. get( B3_TRACE_ID_HEADER ) . is_none( ) ) ;
250+ assert ! ( req_headers. get( B3_SPAN_ID_HEADER ) . is_none( ) ) ;
251+ assert ! ( req_headers. get( B3_SAMPLED_HEADER ) . is_none( ) ) ;
252+
253+ assert_eq ! (
254+ exported_span. trace_id,
255+ Id :: from( Bytes :: from(
256+ hex:: decode( "4bf92f3577b34da6a3ce929d0e0e4736" ) . expect( "decode" )
257+ ) ) ,
258+ ) ;
259+ assert_eq ! (
260+ exported_span. parent_id,
261+ Id :: from( Bytes :: from(
262+ hex:: decode( "00f067aa0ba902b7" ) . expect( "decode" )
263+ ) ) ,
264+ ) ;
265+ assert_ne ! (
266+ exported_span. span_id,
267+ Id :: from( Bytes :: from(
268+ hex:: decode( "00f067aa0ba902b7" ) . expect( "decode" )
269+ ) ) ,
270+ ) ;
271+ }
272+
273+ #[ tokio:: test( flavor = "current_thread" ) ]
274+ async fn b3_propagation ( ) {
275+ let _trace = linkerd_tracing:: test:: trace_init ( ) ;
276+
277+ let ( req_headers, exported_span) = send_mock_request (
278+ http:: Request :: builder ( )
279+ . header ( B3_TRACE_ID_HEADER , "4bf92f3577b34da6a3ce929d0e0e4736" )
280+ . header ( B3_SPAN_ID_HEADER , "00f067aa0ba902b7" )
281+ . header ( B3_SAMPLED_HEADER , "1" )
282+ . body ( BoxBody :: empty ( ) )
283+ . expect ( "request" ) ,
284+ )
285+ . await ;
286+
287+ assert ! ( req_headers. get( W3C_TRACEPARENT_HEADER ) . is_none( ) ) ;
288+ assert ! ( req_headers. get( B3_TRACE_ID_HEADER ) . is_some( ) ) ;
289+ assert ! ( req_headers. get( B3_SPAN_ID_HEADER ) . is_some( ) ) ;
290+ assert ! ( req_headers. get( B3_SAMPLED_HEADER ) . is_some( ) ) ;
291+
292+ assert_eq ! (
293+ exported_span. trace_id,
294+ Id :: from( Bytes :: from(
295+ hex:: decode( "4bf92f3577b34da6a3ce929d0e0e4736" ) . expect( "decode" )
296+ ) ) ,
297+ ) ;
298+ assert_eq ! (
299+ exported_span. parent_id,
300+ Id :: from( Bytes :: from(
301+ hex:: decode( "00f067aa0ba902b7" ) . expect( "decode" )
302+ ) ) ,
303+ ) ;
304+ assert_ne ! (
305+ exported_span. span_id,
306+ Id :: from( Bytes :: from(
307+ hex:: decode( "00f067aa0ba902b7" ) . expect( "decode" )
308+ ) ) ,
309+ ) ;
310+ }
311+
312+ #[ tokio:: test( flavor = "current_thread" ) ]
313+ async fn trace_labels ( ) {
314+ let _trace = linkerd_tracing:: test:: trace_init ( ) ;
315+
316+ let ( _, exported_span) = send_mock_request (
317+ http:: Request :: builder ( )
318+ . uri ( "http://example.com:80/foo?bar=baz" )
319+ . header (
320+ W3C_TRACEPARENT_HEADER ,
321+ "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" ,
322+ )
323+ . header ( "user-agent" , "tokio-test" )
324+ . header ( "content-length" , "0" )
325+ . header ( "content-type" , "text/plain" )
326+ . header ( "l5d-orig-proto" , "HTTP/1.1" )
327+ . body ( BoxBody :: empty ( ) )
328+ . expect ( "request" ) ,
329+ )
330+ . await ;
331+
332+ let labels = exported_span. labels . into_iter ( ) . collect :: < BTreeMap < _ , _ > > ( ) ;
333+ assert_eq ! (
334+ labels,
335+ BTreeMap :: from_iter( [
336+ ( "http.request.header.content-length" , "0" . to_string( ) ) ,
337+ ( "http.request.header.content-type" , "text/plain" . to_string( ) ) ,
338+ ( "http.request.header.l5d-orig-proto" , "HTTP/1.1" . to_string( ) ) ,
339+ ( "http.request.method" , "GET" . to_string( ) ) ,
340+ ( "http.response.status_code" , "200" . to_string( ) ) ,
341+ ( "network.transport" , "tcp" . to_string( ) ) ,
342+ ( "url.full" , "http://example.com:80/foo?bar=baz" . to_string( ) ) ,
343+ ( "url.path" , "/foo" . to_string( ) ) ,
344+ ( "url.query" , "bar=baz" . to_string( ) ) ,
345+ ( "url.scheme" , "http" . to_string( ) ) ,
346+ ( "user_agent.original" , "tokio-test" . to_string( ) )
347+ ] )
348+ )
349+ }
350+
351+ async fn send_mock_request ( req : http:: Request < BoxBody > ) -> ( HeaderMap , Span ) {
352+ let ( span_tx, mut span_rx) = mpsc:: channel ( 1 ) ;
353+
354+ let ( inner, mut handle) =
355+ tower_test:: mock:: pair :: < http:: Request < BoxBody > , http:: Response < BoxBody > > ( ) ;
356+ let mut stack = TraceContext :: < TestSink , _ > :: layer ( TestSink ( span_tx) ) . layer ( inner) ;
357+ handle. allow ( 1 ) ;
358+
359+ let stack = stack. ready ( ) . await . expect ( "ready" ) ;
360+
361+ let ( _, req_headers) : ( http:: Response < BoxBody > , _ ) = tokio:: join! {
362+ stack. call( req) . map( |res| res. expect( "must not fail" ) ) ,
363+ handle. next_request( ) . map( |req| {
364+ let ( req, tx) = req. expect( "request" ) ;
365+ tx. send_response( http:: Response :: default ( ) ) ;
366+ req. headers( ) . clone( )
367+ } ) ,
368+ } ;
369+
370+ (
371+ req_headers,
372+ span_rx. try_recv ( ) . expect ( "must have exported span" ) ,
373+ )
374+ }
375+
376+ #[ derive( Clone ) ]
377+ struct TestSink ( mpsc:: Sender < Span > ) ;
378+
379+ impl SpanSink for TestSink {
380+ fn is_enabled ( & self ) -> bool {
381+ true
382+ }
383+
384+ fn try_send ( & mut self , span : Span ) -> Result < ( ) , Error > {
385+ self . 0 . try_send ( span) ?;
386+ Ok ( ( ) )
387+ }
388+ }
389+ }
0 commit comments