@@ -346,6 +346,7 @@ def __init__(
346346 expected_status : int ,
347347 expected_transaction_name : "Optional[str]" ,
348348 expected_source : "Optional[str]" = None ,
349+ streaming_compatible : bool = True ,
349350 ) -> None :
350351 """
351352 expected_transaction_name of None indicates we expect to not receive a transaction
@@ -355,11 +356,13 @@ def __init__(
355356 self .expected_status = expected_status
356357 self .expected_transaction_name = expected_transaction_name
357358 self .expected_source = expected_source
359+ self .streaming_compatible = streaming_compatible
358360
359361
360362@pytest .mark .skipif (
361363 not PERFORMANCE_SUPPORTED , reason = "Performance not supported on this Sanic version"
362364)
365+ @pytest .mark .parametrize ("span_streaming" , [True , False ])
363366@pytest .mark .parametrize (
364367 "test_config" ,
365368 [
@@ -371,6 +374,14 @@ def __init__(
371374 expected_transaction_name = "hi" ,
372375 expected_source = TransactionSource .COMPONENT ,
373376 ),
377+ TransactionTestConfig (
378+ # Transaction for successful page load with query string
379+ integration_args = (),
380+ url = "/message?foo=bar" ,
381+ expected_status = 200 ,
382+ expected_transaction_name = "hi" ,
383+ expected_source = TransactionSource .COMPONENT ,
384+ ),
374385 TransactionTestConfig (
375386 # Transaction still recorded when we have an internal server error
376387 integration_args = (),
@@ -385,6 +396,7 @@ def __init__(
385396 url = "/404" ,
386397 expected_status = 404 ,
387398 expected_transaction_name = None ,
399+ streaming_compatible = False ,
388400 ),
389401 TransactionTestConfig (
390402 # With no ignored HTTP statuses, we should get transactions for 404 errors
@@ -400,6 +412,7 @@ def __init__(
400412 url = "/message" ,
401413 expected_status = 200 ,
402414 expected_transaction_name = None ,
415+ streaming_compatible = False ,
403416 ),
404417 ],
405418)
@@ -408,57 +421,124 @@ def test_transactions(
408421 sentry_init : "Any" ,
409422 app : "Any" ,
410423 capture_events : "Any" ,
424+ capture_items : "Any" ,
425+ span_streaming : bool ,
411426) -> None :
427+ if span_streaming and not test_config .streaming_compatible :
428+ pytest .skip ("unsampled_statuses is not supported in span streaming mode" )
429+
412430 # Init the SanicIntegration with the desired arguments
413431 sentry_init (
414432 integrations = [SanicIntegration (* test_config .integration_args )],
415433 traces_sample_rate = 1.0 ,
434+ _experiments = {"trace_lifecycle" : "stream" if span_streaming else "static" },
416435 )
417- events = capture_events ()
436+
437+ if span_streaming :
438+ items = capture_items ("span" )
439+ else :
440+ events = capture_events ()
418441
419442 # Make request to the desired URL
420443 c = get_client (app )
421444 with c as client :
422445 _ , response = client .get (test_config .url )
423446 assert response .status == test_config .expected_status
424447
425- # Extract the transaction events by inspecting the event types. We should at most have 1 transaction event.
426- transaction_events = [
427- e for e in events if "type" in e and e ["type" ] == "transaction"
428- ]
429- assert len (transaction_events ) <= 1
430-
431- # Get the only transaction event, or set to None if there are no transaction events.
432- (transaction_event , * _ ) = [* transaction_events , None ]
433-
434- # We should have no transaction event if and only if we expect no transactions
435- assert (transaction_event is None ) == (
436- test_config .expected_transaction_name is None
437- )
448+ sentry_sdk .flush ()
449+
450+ if span_streaming :
451+ segments = [
452+ i .payload
453+ for i in items
454+ if i .type == "span"
455+ and i .payload ["attributes" ].get ("sentry.origin" ) == "auto.http.sanic"
456+ ]
457+ assert len (segments ) <= 1
458+ (segment , * _ ) = [* segments , None ]
459+
460+ assert (segment is None ) == (test_config .expected_transaction_name is None )
461+
462+ if segment is not None :
463+ assert segment ["name" ] == test_config .expected_transaction_name
464+ assert (
465+ segment ["attributes" ]["sentry.span.source" ]
466+ == test_config .expected_source
467+ )
438468
439- # If a transaction was expected, ensure it is correct
440- assert (
441- transaction_event is None
442- or transaction_event ["transaction" ] == test_config .expected_transaction_name
443- )
444- assert (
445- transaction_event is None
446- or transaction_event ["transaction_info" ]["source" ]
447- == test_config .expected_source
448- )
469+ attrs = segment ["attributes" ]
470+ assert attrs ["http.request.method" ] == "GET"
471+ assert attrs ["url.full" ].endswith (test_config .url )
472+ if "?" in test_config .url :
473+ assert attrs ["http.query" ] == test_config .url .split ("?" , 1 )[1 ]
474+ assert attrs ["network.protocol.name" ] == "http"
475+ header_keys = {
476+ key [len ("http.request.header." ) :]
477+ for key in attrs
478+ if key .startswith ("http.request.header." )
479+ }
480+ assert header_keys >= {"accept" , "accept-encoding" , "host" , "user-agent" }
481+ assert attrs ["http.response.status_code" ] == test_config .expected_status
482+ assert segment ["status" ] == (
483+ "error" if test_config .expected_status >= 400 else "ok"
484+ )
485+ else :
486+ # Extract the transaction events by inspecting the event types. We should at most have 1 transaction event.
487+ transaction_events = [
488+ e for e in events if "type" in e and e ["type" ] == "transaction"
489+ ]
490+ assert len (transaction_events ) <= 1
491+
492+ # Get the only transaction event, or set to None if there are no transaction events.
493+ (transaction_event , * _ ) = [* transaction_events , None ]
494+
495+ # We should have no transaction event if and only if we expect no transactions
496+ assert (transaction_event is None ) == (
497+ test_config .expected_transaction_name is None
498+ )
499+
500+ # If a transaction was expected, ensure it is correct
501+ assert (
502+ transaction_event is None
503+ or transaction_event ["transaction" ] == test_config .expected_transaction_name
504+ )
505+ assert (
506+ transaction_event is None
507+ or transaction_event ["transaction_info" ]["source" ]
508+ == test_config .expected_source
509+ )
449510
450511
451512@pytest .mark .skipif (
452513 not PERFORMANCE_SUPPORTED , reason = "Performance not supported on this Sanic version"
453514)
454- def test_span_origin (sentry_init , app , capture_events ):
455- sentry_init (integrations = [SanicIntegration ()], traces_sample_rate = 1.0 )
456- events = capture_events ()
515+ @pytest .mark .parametrize ("span_streaming" , [True , False ])
516+ def test_span_origin (sentry_init , app , capture_events , capture_items , span_streaming ):
517+ sentry_init (
518+ integrations = [SanicIntegration ()],
519+ traces_sample_rate = 1.0 ,
520+ _experiments = {"trace_lifecycle" : "stream" if span_streaming else "static" },
521+ )
522+
523+ if span_streaming :
524+ items = capture_items ("span" )
525+ else :
526+ events = capture_events ()
457527
458528 c = get_client (app )
459529 with c as client :
460530 client .get ("/message?foo=bar" )
461531
462- ( _ , event ) = events
532+ sentry_sdk . flush ()
463533
464- assert event ["contexts" ]["trace" ]["origin" ] == "auto.http.sanic"
534+ if span_streaming :
535+ (segment ,) = [
536+ i .payload
537+ for i in items
538+ if i .type == "span"
539+ and i .payload ["attributes" ].get ("sentry.origin" ) == "auto.http.sanic"
540+ ]
541+ assert segment ["attributes" ]["sentry.origin" ] == "auto.http.sanic"
542+ else :
543+ (_ , event ) = events
544+ assert event ["contexts" ]["trace" ]["origin" ] == "auto.http.sanic"
0 commit comments