From 92e6f1eb9afebbb38ee7c2df8e19bbb569361301 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 10 Sep 2026 11:32:27 +0200 Subject: [PATCH 1/5] chore: Adapt more tests, set transaction name on scope --- sentry_sdk/scope.py | 2 + tests/tracing/test_misc.py | 313 +----------------------------- tests/tracing/test_propagation.py | 23 +-- tests/tracing/test_sample_rand.py | 55 +----- tests/tracing/test_span_name.py | 59 ------ tests/tracing/test_span_origin.py | 41 +--- 6 files changed, 12 insertions(+), 481 deletions(-) delete mode 100644 tests/tracing/test_span_name.py diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 76ec2cda2a..329ad099ac 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -1212,6 +1212,8 @@ def start_streamed_span( if parent_span is None: propagation_context = self.get_active_propagation_context() + self._transaction = name + if is_ignored_span(name, attributes): return NoOpStreamedSpan( name=name, diff --git a/tests/tracing/test_misc.py b/tests/tracing/test_misc.py index f644b722b9..1d1a1c44ae 100644 --- a/tests/tracing/test_misc.py +++ b/tests/tracing/test_misc.py @@ -1,177 +1,12 @@ -from unittest import mock from unittest.mock import MagicMock import pytest import sentry_sdk -from sentry_sdk import start_span, start_transaction from sentry_sdk.consts import MATCH_ALL from sentry_sdk.traces import StreamedSpan -from sentry_sdk.tracing import Span, Transaction from sentry_sdk.tracing_utils import should_propagate_trace from sentry_sdk.utils import Dsn -from tests.conftest import ApproxDict - - -def test_span_trimming(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0, _experiments={"max_spans": 3}) - events = capture_events() - - with start_transaction(name="hi"): - for i in range(10): - with start_span(op="foo{}".format(i)): - pass - - (event,) = events - - assert len(event["spans"]) == 3 - - span1, span2, span3 = event["spans"] - assert span1["op"] == "foo0" - assert span2["op"] == "foo1" - assert span3["op"] == "foo2" - - assert event["_meta"]["spans"][""]["len"] == 10 - assert "_dropped_spans" not in event - assert "dropped_spans" not in event - - -def test_span_trimming_produces_client_report( - sentry_init, capture_events, capture_record_lost_event_calls -): - sentry_init(traces_sample_rate=1.0, _experiments={"max_spans": 3}) - events = capture_events() - record_lost_event_calls = capture_record_lost_event_calls() - - with start_transaction(name="hi"): - for i in range(10): - with start_span(op="foo{}".format(i)): - pass - - (event,) = events - - assert len(event["spans"]) == 3 - - # 7 spans were dropped (10 total - 3 kept = 7 dropped) - assert ("buffer_overflow", "span", None, 7) in record_lost_event_calls - - -def test_span_data_scrubbing_and_trimming(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0, _experiments={"max_spans": 3}) - events = capture_events() - - with start_transaction(name="hi"): - with start_span(op="foo", name="bar") as span: - span.set_data("password", "secret") - span.set_data("datafoo", "databar") - - for i in range(10): - with start_span(op="foo{}".format(i)): - pass - - (event,) = events - assert event["spans"][0]["data"] == ApproxDict( - {"password": "[Filtered]", "datafoo": "databar"} - ) - assert event["_meta"]["spans"] == { - "0": {"data": {"password": {"": {"rem": [["!config", "s"]]}}}}, - "": {"len": 11}, - } - - -def test_transaction_naming(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - # default name in event if no name is passed - with start_transaction() as transaction: - pass - assert len(events) == 1 - assert events[0]["transaction"] == "" - - # the name can be set once the transaction's already started - with start_transaction() as transaction: - transaction.name = "name-known-after-transaction-started" - assert len(events) == 2 - assert events[1]["transaction"] == "name-known-after-transaction-started" - - # passing in a name works, too - with start_transaction(name="a"): - pass - assert len(events) == 3 - assert events[2]["transaction"] == "a" - - -def test_transaction_data(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - with start_transaction(name="test-transaction"): - span_or_tx = sentry_sdk.get_current_span() - span_or_tx.set_data("foo", "bar") - with start_span(op="test-span") as span: - span.set_data("spanfoo", "spanbar") - - assert len(events) == 1 - - transaction = events[0] - transaction_data = transaction["contexts"]["trace"]["data"] - - assert "data" not in transaction.keys() - assert transaction_data.items() >= {"foo": "bar"}.items() - - assert len(transaction["spans"]) == 1 - - span = transaction["spans"][0] - span_data = span["data"] - - assert "contexts" not in span.keys() - assert span_data.items() >= {"spanfoo": "spanbar"}.items() - - -def test_start_transaction(sentry_init): - sentry_init(traces_sample_rate=1.0) - - # you can have it start a transaction for you - result1 = start_transaction( - name="/interactions/other-dogs/new-dog", op="greeting.sniff" - ) - assert isinstance(result1, Transaction) - assert result1.name == "/interactions/other-dogs/new-dog" - assert result1.op == "greeting.sniff" - - # or you can pass it an already-created transaction - preexisting_transaction = Transaction( - name="/interactions/other-dogs/new-dog", op="greeting.sniff" - ) - result2 = start_transaction(preexisting_transaction) - assert result2 is preexisting_transaction - - -def test_finds_transaction_on_scope(sentry_init): - sentry_init(traces_sample_rate=1.0) - - transaction = start_transaction(name="dogpark") - - scope = sentry_sdk.get_current_scope() - - # See note in Scope class re: getters and setters of the `transaction` - # property. For the moment, assigning to scope.transaction merely sets the - # transaction name, rather than putting the transaction on the scope, so we - # have to assign to _span directly. - scope._span = transaction - - # Reading scope.property, however, does what you'd expect, and returns the - # transaction on the scope. - assert scope.transaction is not None - assert isinstance(scope.transaction, Transaction) - assert scope.transaction.name == "dogpark" - - # If the transaction is also set as the span on the scope, it can be found - # by accessing _span, too. - assert scope._span is not None - assert isinstance(scope._span, Transaction) - assert scope._span.name == "dogpark" def test_finds_segment_on_scope(sentry_init): @@ -191,60 +26,7 @@ def test_finds_segment_on_scope(sentry_init): assert scope._span.name == "dogpark" -def test_finds_transaction_when_descendent_span_is_on_scope( - sentry_init, -): - sentry_init(traces_sample_rate=1.0) - - transaction = start_transaction(name="dogpark") - child_span = transaction.start_child(op="sniffing") - - scope = sentry_sdk.get_current_scope() - scope._span = child_span - - # this is the same whether it's the transaction itself or one of its - # decedents directly attached to the scope - assert scope.transaction is not None - assert isinstance(scope.transaction, Transaction) - assert scope.transaction.name == "dogpark" - - # here we see that it is in fact the span on the scope, rather than the - # transaction itself - assert scope._span is not None - assert isinstance(scope._span, Span) - assert scope._span.op == "sniffing" - - -def test_finds_orphan_span_on_scope(sentry_init): - # this is deprecated behavior which may be removed at some point (along with - # the start_span function) - sentry_init(traces_sample_rate=1.0) - - span = start_span(op="sniffing") - - scope = sentry_sdk.get_current_scope() - scope._span = span - - assert scope._span is not None - assert isinstance(scope._span, Span) - assert scope._span.op == "sniffing" - - -def test_finds_non_orphan_span_on_scope(sentry_init): - sentry_init(traces_sample_rate=1.0) - - transaction = start_transaction(name="dogpark") - child_span = transaction.start_child(op="sniffing") - - scope = sentry_sdk.get_current_scope() - scope._span = child_span - - assert scope._span is not None - assert isinstance(scope._span, Span) - assert scope._span.op == "sniffing" - - -def test_finds_non_orphan_span_on_scope_span_streaming(sentry_init): +def test_finds_span_on_scope(sentry_init): sentry_init( traces_sample_rate=1.0, trace_lifecycle="stream", @@ -335,97 +117,10 @@ def test_should_propagate_trace_to_sentry( assert should_propagate_trace(client, url) == expected_propagation_decision -def test_start_transaction_updates_scope_name_source(sentry_init): - sentry_init(traces_sample_rate=1.0) +def test_start_transaction_updates_scope_name(sentry_init): + sentry_init(traces_sample_rate=1.0, trace_lifecycle="stream") scope = sentry_sdk.get_current_scope() - with start_transaction(name="foobar", source="route"): + with sentry_sdk.traces.start_span(name="foobar"): assert scope._transaction == "foobar" - assert scope._transaction_info == {"source": "route"} - - -@pytest.mark.parametrize("sampled", (True, None)) -def test_transaction_dropped_debug_not_started(sentry_init, sampled): - sentry_init(traces_sample_rate=1.0) - - tx = Transaction(sampled=sampled) - - with mock.patch("sentry_sdk.tracing.logger") as mock_logger: - with tx: - pass - - mock_logger.debug.assert_any_call( - "Discarding transaction because it was not started with sentry_sdk.start_transaction" - ) - - with pytest.raises(AssertionError): - # We should NOT see the "sampled = False" message here - mock_logger.debug.assert_any_call( - "Discarding transaction because sampled = False" - ) - - -def test_transaction_dropped_sampled_false(sentry_init): - sentry_init(traces_sample_rate=1.0) - - tx = Transaction(sampled=False) - - with mock.patch("sentry_sdk.tracing.logger") as mock_logger: - with sentry_sdk.start_transaction(tx): - pass - - mock_logger.debug.assert_any_call("Discarding transaction because sampled = False") - - with pytest.raises(AssertionError): - # We should not see the "not started" message here - mock_logger.debug.assert_any_call( - "Discarding transaction because it was not started with sentry_sdk.start_transaction" - ) - - -def test_transaction_not_started_warning(sentry_init): - sentry_init(traces_sample_rate=1.0) - - tx = Transaction() - - with mock.patch("sentry_sdk.tracing.logger") as mock_logger: - with tx: - pass - - mock_logger.debug.assert_any_call( - "Transaction was entered without being started with sentry_sdk.start_transaction." - "The transaction will not be sent to Sentry. To fix, start the transaction by" - "passing it to sentry_sdk.start_transaction." - ) - - -def test_span_set_data_update_data(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0) - - events = capture_events() - - with sentry_sdk.start_transaction(name="test-transaction"): - with start_span(op="test-span") as span: - span.set_data("key0", "value0") - span.set_data("key1", "value1") - - span.update_data( - { - "key1": "updated-value1", - "key2": "value2", - "key3": "value3", - } - ) - - (event,) = events - span = event["spans"][0] - - assert span["data"] == { - "key0": "value0", - "key1": "updated-value1", - "key2": "value2", - "key3": "value3", - "thread.id": mock.ANY, - "thread.name": mock.ANY, - } diff --git a/tests/tracing/test_propagation.py b/tests/tracing/test_propagation.py index cbecfeeb2c..29b1af295a 100644 --- a/tests/tracing/test_propagation.py +++ b/tests/tracing/test_propagation.py @@ -22,16 +22,7 @@ def test_span_in_span_iter_headers(sentry_init): next(span_inner.iter_headers()) -def test_span_in_transaction(sentry_init): - sentry_init(traces_sample_rate=1.0) - - with sentry_sdk.start_transaction(op="test"): - with sentry_sdk.start_span(op="test2") as span: - # Ensure the headers are there - next(span.iter_headers()) - - -def test_span_in_transaction_span_streaming(sentry_init): +def test_span_in_segment(sentry_init): sentry_init(traces_sample_rate=1.0, trace_lifecycle="stream") with sentry_sdk.traces.start_span(name="test"): @@ -40,17 +31,7 @@ def test_span_in_transaction_span_streaming(sentry_init): next(span._iter_headers()) -def test_span_in_span_in_transaction(sentry_init): - sentry_init(traces_sample_rate=1.0) - - with sentry_sdk.start_transaction(op="test"): - with sentry_sdk.start_span(op="test2"): - with sentry_sdk.start_span(op="test3") as span_inner: - # Ensure the headers are there - next(span_inner.iter_headers()) - - -def test_span_in_span_in_transaction_span_streaming(sentry_init): +def test_span_in_span_in_segment(sentry_init): sentry_init(traces_sample_rate=1.0, trace_lifecycle="stream") with sentry_sdk.traces.start_span(name="test"): diff --git a/tests/tracing/test_sample_rand.py b/tests/tracing/test_sample_rand.py index e9835d1de1..cf31750042 100644 --- a/tests/tracing/test_sample_rand.py +++ b/tests/tracing/test_sample_rand.py @@ -3,7 +3,6 @@ import pytest import sentry_sdk -from sentry_sdk.tracing_utils import Baggage # Boundary cases for the sampling decision `sample_rand < sample_rate`: # equality (strict <), below, above, and the degenerate rates 0.0 (never @@ -20,34 +19,7 @@ @pytest.mark.parametrize("sample_rand,sample_rate", SAMPLE_RAND_RATE_CASES) -def test_deterministic_sampled(sentry_init, capture_events, sample_rate, sample_rand): - """ - Test that sample_rand is generated on new traces, that it is used to - make the sampling decision, and that it is included in the transaction's - baggage. - """ - sentry_init(traces_sample_rate=sample_rate) - events = capture_events() - - with mock.patch( - "sentry_sdk.tracing_utils.Random.randrange", - return_value=int(sample_rand * 1000000), - ): - with sentry_sdk.start_transaction() as transaction: - assert ( - transaction.get_baggage().sentry_items["sample_rand"] - == f"{sample_rand:.6f}" # noqa: E231 - ) - - # Transaction event captured if sample_rand < sample_rate, indicating that - # sample_rand is used to make the sampling decision. - assert len(events) == int(sample_rand < sample_rate) - - -@pytest.mark.parametrize("sample_rand,sample_rate", SAMPLE_RAND_RATE_CASES) -def test_deterministic_sampled_span_streaming( - sentry_init, capture_items, sample_rate, sample_rand -): +def test_deterministic_sampled(sentry_init, capture_items, sample_rate, sample_rand): """ Test that sample_rand is generated on new traces, that it is used to make the sampling decision, and that it is included in the segment's @@ -76,30 +48,7 @@ def test_deterministic_sampled_span_streaming( @pytest.mark.parametrize("sample_rand,sample_rate", SAMPLE_RAND_RATE_CASES) -def test_transaction_uses_incoming_sample_rand( - sentry_init, capture_events, sample_rate, sample_rand -): - """ - Test that the transaction uses the sample_rand value from the incoming baggage. - """ - baggage = Baggage(sentry_items={"sample_rand": f"{sample_rand:.6f}"}) # noqa: E231 - - sentry_init(traces_sample_rate=sample_rate) - events = capture_events() - - with sentry_sdk.start_transaction(baggage=baggage) as transaction: - assert ( - transaction.get_baggage().sentry_items["sample_rand"] - == f"{sample_rand:.6f}" # noqa: E231 - ) - - # Transaction event captured if sample_rand < sample_rate, indicating that - # sample_rand is used to make the sampling decision. - assert len(events) == int(sample_rand < sample_rate) - - -@pytest.mark.parametrize("sample_rand,sample_rate", SAMPLE_RAND_RATE_CASES) -def test_segment_uses_incoming_sample_rand_span_streaming( +def test_segment_uses_incoming_sample_rand( sentry_init, capture_items, sample_rate, sample_rand ): """ diff --git a/tests/tracing/test_span_name.py b/tests/tracing/test_span_name.py deleted file mode 100644 index 9c1768990a..0000000000 --- a/tests/tracing/test_span_name.py +++ /dev/null @@ -1,59 +0,0 @@ -import pytest - -import sentry_sdk - - -def test_start_span_description(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - with sentry_sdk.start_transaction(name="hi"): - with pytest.deprecated_call(): - with sentry_sdk.start_span(op="foo", description="span-desc"): - ... - - (event,) = events - - assert event["spans"][0]["description"] == "span-desc" - - -def test_start_span_name(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - with sentry_sdk.start_transaction(name="hi"): - with sentry_sdk.start_span(op="foo", name="span-name"): - ... - - (event,) = events - - assert event["spans"][0]["description"] == "span-name" - - -def test_start_child_description(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - with sentry_sdk.start_transaction(name="hi"): - with pytest.deprecated_call(): - with sentry_sdk.start_span(op="foo", description="span-desc") as span: - with span.start_child(op="bar", description="child-desc"): - ... - - (event,) = events - - assert event["spans"][-1]["description"] == "child-desc" - - -def test_start_child_name(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - with sentry_sdk.start_transaction(name="hi"): - with sentry_sdk.start_span(op="foo", name="span-name") as span: - with span.start_child(op="bar", name="child-name"): - ... - - (event,) = events - - assert event["spans"][-1]["description"] == "child-name" diff --git a/tests/tracing/test_span_origin.py b/tests/tracing/test_span_origin.py index fbdf8c356b..f36b4ab84e 100644 --- a/tests/tracing/test_span_origin.py +++ b/tests/tracing/test_span_origin.py @@ -1,44 +1,7 @@ import sentry_sdk -def test_span_origin_manual(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - with sentry_sdk.start_transaction(name="hi"): - with sentry_sdk.start_span(op="foo", name="bar"): - pass - - (event,) = events - - assert len(events) == 1 - assert event["spans"][0]["origin"] == "manual" - assert event["contexts"]["trace"]["origin"] == "manual" - - -def test_span_origin_custom(sentry_init, capture_events): - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - with sentry_sdk.start_transaction(name="hi"): - with sentry_sdk.start_span(op="foo", name="bar", origin="foo.foo2.foo3"): - pass - - with sentry_sdk.start_transaction(name="ho", origin="ho.ho2.ho3"): - with sentry_sdk.start_span(op="baz", name="qux", origin="baz.baz2.baz3"): - pass - - (first_transaction, second_transaction) = events - - assert len(events) == 2 - assert first_transaction["contexts"]["trace"]["origin"] == "manual" - assert first_transaction["spans"][0]["origin"] == "foo.foo2.foo3" - - assert second_transaction["contexts"]["trace"]["origin"] == "ho.ho2.ho3" - assert second_transaction["spans"][0]["origin"] == "baz.baz2.baz3" - - -def test_span_origin_manual_span_streaming(sentry_init, capture_items): +def test_span_origin_manual(sentry_init, capture_items): sentry_init(trace_lifecycle="stream", traces_sample_rate=1.0) items = capture_items("span") @@ -53,7 +16,7 @@ def test_span_origin_manual_span_streaming(sentry_init, capture_items): assert span["attributes"]["sentry.origin"] == "manual" -def test_span_origin_custom_span_streaming(sentry_init, capture_items): +def test_span_origin_custom(sentry_init, capture_items): sentry_init(trace_lifecycle="stream", traces_sample_rate=1.0) items = capture_items("span") From 315dad72f66835d86529877e70c329afe0729031 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 10 Sep 2026 11:45:44 +0200 Subject: [PATCH 2/5] . --- sentry_sdk/scope.py | 2 -- tests/tracing/test_misc.py | 5 ++++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 329ad099ac..76ec2cda2a 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -1212,8 +1212,6 @@ def start_streamed_span( if parent_span is None: propagation_context = self.get_active_propagation_context() - self._transaction = name - if is_ignored_span(name, attributes): return NoOpStreamedSpan( name=name, diff --git a/tests/tracing/test_misc.py b/tests/tracing/test_misc.py index 1d1a1c44ae..873abdbec2 100644 --- a/tests/tracing/test_misc.py +++ b/tests/tracing/test_misc.py @@ -122,5 +122,8 @@ def test_start_transaction_updates_scope_name(sentry_init): scope = sentry_sdk.get_current_scope() - with sentry_sdk.traces.start_span(name="foobar"): + with sentry_sdk.traces.start_span( + name="foobar", attributes={"sentry.segment.name.source": "test"} + ): assert scope._transaction == "foobar" + assert scope._transaction_info == {"source": "test"} From 1393e6f5c326d6c8c852e5f159e90ea25e332269 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 10 Sep 2026 12:09:03 +0200 Subject: [PATCH 3/5] integration tests --- tests/tracing/test_integration_tests.py | 424 +----------------------- 1 file changed, 9 insertions(+), 415 deletions(-) diff --git a/tests/tracing/test_integration_tests.py b/tests/tracing/test_integration_tests.py index 99f37bf8e5..97f253bcaf 100644 --- a/tests/tracing/test_integration_tests.py +++ b/tests/tracing/test_integration_tests.py @@ -7,57 +7,14 @@ import pytest import sentry_sdk -from sentry_sdk import ( - capture_message, - continue_trace, - start_span, - start_transaction, -) +from sentry_sdk import capture_message from sentry_sdk.consts import SPANSTATUS from sentry_sdk.transport import Transport from tests.conftest import TestTransportWithOptions @pytest.mark.parametrize("sample_rate", [0.0, 1.0]) -def test_basic(sentry_init, capture_events, sample_rate): - sentry_init(traces_sample_rate=sample_rate) - events = capture_events() - - with start_transaction(name="hi") as transaction: - transaction.set_status(SPANSTATUS.OK) - with pytest.raises(ZeroDivisionError): - with start_span(op="foo", name="foodesc"): - 1 / 0 - - with start_span(op="bar", name="bardesc"): - pass - - if sample_rate: - assert len(events) == 1 - event = events[0] - - assert event["transaction"] == "hi" - assert event["transaction_info"]["source"] == "custom" - - span1, span2 = event["spans"] - parent_span = event - assert span1["status"] == "internal_error" - assert span1["tags"]["status"] == "internal_error" - assert span1["op"] == "foo" - assert span1["description"] == "foodesc" - assert "status" not in span2 - assert "status" not in span2.get("tags", {}) - assert span2["op"] == "bar" - assert span2["description"] == "bardesc" - assert parent_span["transaction"] == "hi" - assert "status" not in event["tags"] - assert event["contexts"]["trace"]["status"] == "ok" - else: - assert not events - - -@pytest.mark.parametrize("sample_rate", [0.0, 1.0]) -def test_basic_span_streaming(sentry_init, capture_items, sample_rate): +def test_basic(sentry_init, capture_items, sample_rate): sentry_init(traces_sample_rate=sample_rate, trace_lifecycle="stream") items = capture_items() @@ -91,9 +48,7 @@ def test_basic_span_streaming(sentry_init, capture_items, sample_rate): assert not items -def test_error_event_linked_without_performance_span_streaming( - sentry_init, capture_items -): +def test_error_event_linked_without_performance(sentry_init, capture_items): sentry_init(traces_sample_rate=None, trace_lifecycle="stream") items = capture_items("event") @@ -112,104 +67,7 @@ def test_error_event_linked_without_performance_span_streaming( @pytest.mark.parametrize("parent_sampled", [True, False, None]) @pytest.mark.parametrize("sample_rate", [0.0, 1.0]) -def test_continue_trace(sentry_init, capture_envelopes, parent_sampled, sample_rate): - """ - Ensure data is actually passed along via headers, and that they are read - correctly. - """ - sentry_init(traces_sample_rate=sample_rate) - envelopes = capture_envelopes() - - # make a parent transaction (normally this would be in a different service) - with start_transaction(name="hi", sampled=True if sample_rate == 0 else None): - with start_span() as old_span: - old_span.sampled = parent_sampled - headers = dict( - sentry_sdk.get_current_scope().iter_trace_propagation_headers(old_span) - ) - headers["baggage"] = ( - "other-vendor-value-1=foo;bar;baz, " - "sentry-trace_id=771a43a4192642f0b136d5159a501700, " - "sentry-public_key=49d0f7386ad645858ae85020e393bef3, " - "sentry-sample_rate=0.01337, sentry-user_id=Amelie, " - "sentry-sample_rand=0.250000, " - "other-vendor-value-2=foo;bar;" - ) - - # child transaction, to prove that we can read 'sentry-trace' header data correctly - child_transaction = continue_trace(headers, name="WRONG") - assert child_transaction is not None - assert child_transaction.parent_sampled == parent_sampled - assert child_transaction.trace_id == old_span.trace_id - assert child_transaction.same_process_as_parent is False - assert child_transaction.parent_span_id == old_span.span_id - assert child_transaction.span_id != old_span.span_id - - baggage = child_transaction._baggage - assert baggage - assert not baggage.mutable - assert baggage.sentry_items == { - "public_key": "49d0f7386ad645858ae85020e393bef3", - "trace_id": "771a43a4192642f0b136d5159a501700", - "user_id": "Amelie", - "sample_rand": "0.250000", - "sample_rate": "0.01337", - } - - # add child transaction to the scope, to show that the captured message will - # be tagged with the trace id (since it happens while the transaction is - # open) - with start_transaction(child_transaction): - # change the transaction name from "WRONG" to make sure the change - # is reflected in the final data - sentry_sdk.get_current_scope().transaction = "ho" - capture_message("hello") - - if parent_sampled is False or (sample_rate == 0 and parent_sampled is None): - # in this case the child transaction won't be captured - trace1, message = envelopes - message_payload = message.get_event() - trace1_payload = trace1.get_transaction_event() - - assert trace1_payload["transaction"] == "hi" - else: - trace1, message, trace2 = envelopes - trace1_payload = trace1.get_transaction_event() - message_payload = message.get_event() - trace2_payload = trace2.get_transaction_event() - - assert trace1_payload["transaction"] == "hi" - assert trace2_payload["transaction"] == "ho" - - assert ( - trace1_payload["contexts"]["trace"]["trace_id"] - == trace2_payload["contexts"]["trace"]["trace_id"] - == child_transaction.trace_id - == message_payload["contexts"]["trace"]["trace_id"] - ) - - if parent_sampled is not None: - expected_sample_rate = str(float(parent_sampled)) - else: - expected_sample_rate = str(sample_rate) - - assert trace2.headers["trace"] == baggage.dynamic_sampling_context() - assert trace2.headers["trace"] == { - "public_key": "49d0f7386ad645858ae85020e393bef3", - "trace_id": "771a43a4192642f0b136d5159a501700", - "user_id": "Amelie", - "sample_rand": "0.250000", - "sample_rate": expected_sample_rate, - } - - assert message_payload["message"] == "hello" - - -@pytest.mark.parametrize("parent_sampled", [True, False, None]) -@pytest.mark.parametrize("sample_rate", [0.0, 1.0]) -def test_continue_trace_span_streaming( - sentry_init, capture_items, parent_sampled, sample_rate -): +def test_continue_trace(sentry_init, capture_items, parent_sampled, sample_rate): """ Ensure data is actually passed along via headers, and that they are read correctly. @@ -333,66 +191,6 @@ def test_continue_trace_span_streaming( @pytest.mark.parametrize("sample_rate", [0.5, 1.0]) def test_dynamic_sampling_head_sdk_creates_dsc( sentry_init, capture_envelopes, sample_rate, monkeypatch -): - sentry_init(traces_sample_rate=sample_rate, release="foo") - envelopes = capture_envelopes() - - # make sure transaction is sampled for both cases - with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=250000): - transaction = continue_trace({}, name="Head SDK tx") - - baggage = transaction._baggage - assert baggage is None - - with start_transaction(transaction): - with start_span(op="foo", name="foodesc"): - pass - - # finish will create a new baggage entry - baggage = transaction._baggage - trace_id = transaction.trace_id - - assert baggage - assert not baggage.mutable - assert baggage.third_party_items == "" - assert baggage.sentry_items == { - "environment": "production", - "release": "foo", - "sample_rate": str(sample_rate), - "sampled": "true" if transaction.sampled else "false", - "sample_rand": "0.250000", - "transaction": "Head SDK tx", - "trace_id": trace_id, - } - - expected_baggage = ( - "sentry-trace_id=%s," - "sentry-sample_rand=0.250000," - "sentry-environment=production," - "sentry-release=foo," - "sentry-transaction=Head%%20SDK%%20tx," - "sentry-sample_rate=%s," - "sentry-sampled=%s" - % (trace_id, sample_rate, "true" if transaction.sampled else "false") - ) - assert baggage.serialize() == expected_baggage - - (envelope,) = envelopes - assert envelope.headers["trace"] == baggage.dynamic_sampling_context() - assert envelope.headers["trace"] == { - "environment": "production", - "release": "foo", - "sample_rate": str(sample_rate), - "sample_rand": "0.250000", - "sampled": "true" if transaction.sampled else "false", - "transaction": "Head SDK tx", - "trace_id": trace_id, - } - - -@pytest.mark.parametrize("sample_rate", [0.5, 1.0]) -def test_dynamic_sampling_head_sdk_creates_dsc_span_streaming( - sentry_init, capture_envelopes, sample_rate, monkeypatch ): sentry_init( traces_sample_rate=sample_rate, @@ -453,40 +251,11 @@ def test_dynamic_sampling_head_sdk_creates_dsc_span_streaming( } -@pytest.mark.parametrize( - "args,expected_refcount", - [({"traces_sample_rate": 1.0}, 100), ({"traces_sample_rate": 0.0}, 0)], -) -def test_memory_usage(sentry_init, capture_events, args, expected_refcount): - sentry_init(**args) - - references = weakref.WeakSet() - - with start_transaction(name="hi"): - for i in range(100): - with start_span(op="helloworld", name="hi {}".format(i)) as span: - - def foo(): - pass - - references.add(foo) - span.set_tag("foo", foo) - pass - - del foo - del span - - # required only for pypy (cpython frees immediately) - gc.collect() - - assert len(references) == expected_refcount - - @pytest.mark.parametrize( "args", [{"traces_sample_rate": 1.0}, {"traces_sample_rate": 0.0}], ) -def test_memory_usage_span_streaming(sentry_init, capture_events, args): +def test_memory_usage(sentry_init, capture_events, args): sentry_init(**args, trace_lifecycle="stream") references = weakref.WeakSet() @@ -512,19 +281,6 @@ def foo(): assert len(references) == 0 -def test_transactions_do_not_go_through_before_send(sentry_init, capture_events): - def before_send(event, hint): - raise RuntimeError("should not be called") - - sentry_init(traces_sample_rate=1.0, before_send=before_send) - events = capture_events() - - with start_transaction(name="/"): - pass - - assert len(events) == 1 - - def test_segments_do_not_go_through_before_send(sentry_init, capture_items): def before_send(event, hint): raise RuntimeError("should not be called") @@ -544,26 +300,7 @@ def before_send(event, hint): assert len(items) == 1 -def test_start_span_after_finish(sentry_init, capture_events): - class CustomTransport(Transport): - def capture_envelope(self, envelope): - pass - - def capture_event(self, event): - start_span(op="toolate", name="justdont") - pass - - sentry_init(traces_sample_rate=1, transport=CustomTransport()) - events = capture_events() - - with start_transaction(name="hi"): - with start_span(op="bar", name="bardesc"): - pass - - assert len(events) == 1 - - -def test_start_span_after_finish_span_streaming(sentry_init, capture_items): +def test_start_span_after_finish(sentry_init, capture_items): class CustomTransport(Transport): def capture_envelope(self, envelope): with sentry_sdk.traces.start_span(name="toolate"): @@ -591,30 +328,6 @@ def capture_event(self, event): def test_trace_propagation_meta_head_sdk(sentry_init): - sentry_init(traces_sample_rate=1.0, release="foo") - - transaction = continue_trace({}, name="Head SDK tx") - meta = None - span = None - - with start_transaction(transaction): - with start_span(op="foo", name="foodesc") as current_span: - span = current_span - meta = sentry_sdk.get_current_scope().trace_propagation_meta() - - ind = meta.find(">") + 1 - sentry_trace, baggage = meta[:ind], meta[ind:] - - assert 'meta name="sentry-trace"' in sentry_trace - sentry_trace_content = re.findall('content="([^"]*)"', sentry_trace)[0] - assert sentry_trace_content == span.to_traceparent() - - assert 'meta name="baggage"' in baggage - baggage_content = re.findall('content="([^"]*)"', baggage)[0] - assert baggage_content == transaction.get_baggage().serialize() - - -def test_trace_propagation_meta_head_sdk_span_streaming(sentry_init): sentry_init( traces_sample_rate=1.0, release="foo", @@ -650,34 +363,6 @@ def test_trace_propagation_meta_head_sdk_span_streaming(sentry_init): ], ) def test_non_error_exceptions( - sentry_init, capture_events, exception_cls, exception_value -): - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - with start_transaction(name="hi") as transaction: - transaction.set_status(SPANSTATUS.OK) - with pytest.raises(exception_cls): - with start_span(op="foo", name="foodesc"): - raise exception_cls(exception_value) - - assert len(events) == 1 - event = events[0] - - span = event["spans"][0] - assert "status" not in span - assert "status" not in span.get("tags", {}) - assert "status" not in event["tags"] - assert event["contexts"]["trace"]["status"] == "ok" - - -@pytest.mark.parametrize( - "exception_cls,exception_value", - [ - (SystemExit, 0), - ], -) -def test_non_error_exceptions_span_streaming( sentry_init, capture_items, exception_cls, exception_value ): sentry_init(traces_sample_rate=1.0, trace_lifecycle="stream") @@ -699,35 +384,7 @@ def test_non_error_exceptions_span_streaming( @pytest.mark.parametrize("exception_value", [None, 0, False]) -def test_good_sysexit_doesnt_fail_transaction( - sentry_init, capture_events, exception_value -): - sentry_init(traces_sample_rate=1.0) - events = capture_events() - - with start_transaction(name="hi") as transaction: - transaction.set_status(SPANSTATUS.OK) - with pytest.raises(SystemExit): - with start_span(op="foo", name="foodesc"): - if exception_value is not False: - sys.exit(exception_value) - else: - sys.exit() - - assert len(events) == 1 - event = events[0] - - span = event["spans"][0] - assert "status" not in span - assert "status" not in span.get("tags", {}) - assert "status" not in event["tags"] - assert event["contexts"]["trace"]["status"] == "ok" - - -@pytest.mark.parametrize("exception_value", [None, 0, False]) -def test_good_sysexit_doesnt_fail_segment_span_streaming( - sentry_init, capture_items, exception_value -): +def test_good_sysexit_doesnt_fail_segment(sentry_init, capture_items, exception_value): sentry_init(traces_sample_rate=1.0, trace_lifecycle="stream") items = capture_items() @@ -767,58 +424,6 @@ def test_continue_trace_strict_trace_continuation( baggage_org_id, dsn_org_id, should_continue_trace, -): - sentry_init( - dsn=f"https://mysecret@{dsn_org_id}.ingest.sentry.io/12312012", - strict_trace_continuation=strict_trace_continuation, - traces_sample_rate=1.0, - transport=TestTransportWithOptions, - ) - - headers = { - "sentry-trace": "771a43a4192642f0b136d5159a501700-1234567890abcdef-1", - "baggage": ( - "other-vendor-value-1=foo;bar;baz," - "sentry-trace_id=771a43a4192642f0b136d5159a501700," - f"{baggage_org_id}," - "sentry-public_key=49d0f7386ad645858ae85020e393bef3," - "sentry-sample_rate=0.01337," - "sentry-user_id=Am%C3%A9lie," - "other-vendor-value-2=foo;bar;" - ), - } - - transaction = continue_trace(headers, name="strict trace") - - if should_continue_trace: - assert transaction.trace_id == "771a43a4192642f0b136d5159a501700" - assert transaction.parent_span_id == "1234567890abcdef" - assert transaction.parent_sampled - else: - assert transaction.trace_id != "771a43a4192642f0b136d5159a501700" - assert transaction.parent_span_id != "1234567890abcdef" - assert transaction.parent_sampled is None - - -@pytest.mark.parametrize( - "strict_trace_continuation,baggage_org_id,dsn_org_id,should_continue_trace", - ( - (True, "sentry-org_id=1234", "o1234", True), - (True, "sentry-org_id=1234", "o9999", False), - (True, "sentry-org_id=9999", "o1234", False), - (False, "sentry-org_id=1234", "o1234", True), - (False, "sentry-org_id=9999", "o1234", False), - (False, "sentry-org_id=1234", "o9999", False), - (False, "sentry-org_id=1234", "not_org_id", True), - (False, "", "o1234", True), - ), -) -def test_continue_trace_strict_trace_continuation_span_streaming( - sentry_init, - strict_trace_continuation, - baggage_org_id, - dsn_org_id, - should_continue_trace, ): sentry_init( dsn=f"https://mysecret@{dsn_org_id}.ingest.sentry.io/12312012", @@ -857,18 +462,7 @@ def test_continue_trace_strict_trace_continuation_span_streaming( assert segment._parent_sampled is None -def test_continue_trace_forces_new_traces_when_no_propagation(sentry_init): - """This is to make sure we don't have a long running trace because of TWP logic for the no propagation case.""" - - sentry_init(traces_sample_rate=1.0) - - tx1 = continue_trace({}, name="tx1") - tx2 = continue_trace({}, name="tx2") - - assert tx1.trace_id != tx2.trace_id - - -def test_continue_trace_forces_new_traces_when_no_propagation_span_streaming( +def test_continue_trace_forces_new_traces_when_no_propagation( sentry_init, ): """This is to make sure we don't have a long running trace because of TWP logic for the no propagation case.""" @@ -886,7 +480,7 @@ def test_continue_trace_forces_new_traces_when_no_propagation_span_streaming( assert segment1.trace_id != segment2.trace_id -def test_continue_trace_forces_new_traces_when_no_propagation_with_new_trace_span_streaming( +def test_continue_trace_forces_new_traces_when_no_propagation_with_new_trace( sentry_init, ): sentry_init(traces_sample_rate=1.0, trace_lifecycle="stream") From fbc2c3157ffc9d96c53fae6996758fee3521faa7 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 11 Sep 2026 09:44:59 +0200 Subject: [PATCH 4/5] . --- sentry_sdk/traces.py | 2 +- tests/tracing/test_misc.py | 2 +- tests/tracing/test_sampling.py | 327 +++++---------------------------- 3 files changed, 45 insertions(+), 286 deletions(-) diff --git a/sentry_sdk/traces.py b/sentry_sdk/traces.py index 1f96f4a383..dd26a73f34 100644 --- a/sentry_sdk/traces.py +++ b/sentry_sdk/traces.py @@ -668,7 +668,7 @@ def __init__( self._start() def __repr__(self) -> str: - return f"<{self.__class__.__name__}(sampled={self.sampled})>" + return f"<{self.__class__.__name__}(name={self.name}, sampled={self.sampled})>" def __enter__(self) -> "NoOpStreamedSpan": return self diff --git a/tests/tracing/test_misc.py b/tests/tracing/test_misc.py index 873abdbec2..3c25506224 100644 --- a/tests/tracing/test_misc.py +++ b/tests/tracing/test_misc.py @@ -117,7 +117,7 @@ def test_should_propagate_trace_to_sentry( assert should_propagate_trace(client, url) == expected_propagation_decision -def test_start_transaction_updates_scope_name(sentry_init): +def test_start_span_segment_updates_scope_name(sentry_init): sentry_init(traces_sample_rate=1.0, trace_lifecycle="stream") scope = sentry_sdk.get_current_scope() diff --git a/tests/tracing/test_sampling.py b/tests/tracing/test_sampling.py index 0685498d13..cb41296bae 100644 --- a/tests/tracing/test_sampling.py +++ b/tests/tracing/test_sampling.py @@ -5,24 +5,10 @@ import pytest import sentry_sdk -from sentry_sdk import capture_exception, start_span, start_transaction -from sentry_sdk.tracing_utils import Baggage +from sentry_sdk import capture_exception from sentry_sdk.utils import logger -def test_sampling_decided_only_for_transactions(sentry_init, capture_events): - sentry_init(traces_sample_rate=0.5) - - with start_transaction(name="hi") as transaction: - assert transaction.sampled is not None - - with start_span() as span: - assert span.sampled == transaction.sampled - - with start_span() as span: - assert span.sampled is None - - def test_sampling_decided_only_for_segments(sentry_init, capture_events): sentry_init( traces_sample_rate=0.5, @@ -36,32 +22,7 @@ def test_sampling_decided_only_for_segments(sentry_init, capture_events): assert span.sampled == segment.sampled -@pytest.mark.parametrize("sampled", [True, False]) -def test_nested_transaction_sampling_override(sentry_init, sampled): - sentry_init(traces_sample_rate=1.0) - - with start_transaction(name="outer", sampled=sampled) as outer_transaction: - assert outer_transaction.sampled is sampled - with start_transaction( - name="inner", sampled=(not sampled) - ) as inner_transaction: - assert inner_transaction.sampled is not sampled - assert outer_transaction.sampled is sampled - - -def test_no_double_sampling(sentry_init, capture_events): - # Transactions should not be subject to the global/error sample rate. - # Only the traces_sample_rate should apply. - sentry_init(traces_sample_rate=1.0, sample_rate=0.0) - events = capture_events() - - with start_transaction(name="/"): - pass - - assert len(events) == 1 - - -def test_no_double_sampling_span_streaming(sentry_init, capture_items): +def test_no_double_sampling(sentry_init, capture_items): # Segments should not be subject to the global/error sample rate. # Only the traces_sample_rate should apply. sentry_init( @@ -80,16 +41,26 @@ def test_no_double_sampling_span_streaming(sentry_init, capture_items): @pytest.mark.parametrize("sampling_decision", [True, False]) -def test_get_transaction_and_span_from_scope_regardless_of_sampling_decision( +def test_get_span_from_scope_regardless_of_sampling_decision( sentry_init, sampling_decision ): - sentry_init(traces_sample_rate=1.0) + sentry_init(traces_sample_rate=1.0, trace_lifecycle="stream") - with start_transaction(name="/", sampled=sampling_decision): - with start_span(op="child-span"): - with start_span(op="child-child-span"): + sentry_sdk.traces.continue_trace( + { + "sentry-trace": f"0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-{int(sampling_decision)}" + } + ) + + with sentry_sdk.traces.start_span(name="/"): + with sentry_sdk.traces.start_span(name="child-span"): + with sentry_sdk.traces.start_span(name="child-child-span"): scope = sentry_sdk.get_current_scope() - assert scope.span.op == "child-child-span" + if sampling_decision is True: + assert scope.streamed_span.name == "child-child-span" + else: + # noop spans are not set on the scope unless they're segments + assert scope.streamed_span.name == "/" assert scope.transaction.name == "/" @@ -101,22 +72,6 @@ def test_uses_traces_sample_rate_correctly( sentry_init, traces_sample_rate, expected_decision, -): - sentry_init(traces_sample_rate=traces_sample_rate) - - baggage = Baggage(sentry_items={"sample_rand": "0.500000"}) - transaction = start_transaction(name="dogpark", baggage=baggage) - assert transaction.sampled is expected_decision - - -@pytest.mark.parametrize( - "traces_sample_rate,expected_decision", - [(0.0, False), (0.25, False), (0.75, True), (1.00, True)], -) -def test_uses_traces_sample_rate_correctly_span_streaming( - sentry_init, - traces_sample_rate, - expected_decision, ): sentry_init( traces_sample_rate=traces_sample_rate, @@ -142,22 +97,6 @@ def test_uses_traces_sampler_return_value_correctly( sentry_init, traces_sampler_return_value, expected_decision, -): - sentry_init(traces_sampler=mock.Mock(return_value=traces_sampler_return_value)) - - baggage = Baggage(sentry_items={"sample_rand": "0.500000"}) - transaction = start_transaction(name="dogpark", baggage=baggage) - assert transaction.sampled is expected_decision - - -@pytest.mark.parametrize( - "traces_sampler_return_value,expected_decision", - [(0.0, False), (0.25, False), (0.75, True), (1.00, True)], -) -def test_uses_traces_sampler_return_value_correctly_span_streaming( - sentry_init, - traces_sampler_return_value, - expected_decision, ): sentry_init( traces_sampler=mock.Mock(return_value=traces_sampler_return_value), @@ -178,16 +117,6 @@ def test_uses_traces_sampler_return_value_correctly_span_streaming( @pytest.mark.parametrize("traces_sampler_return_value", [True, False]) def test_tolerates_traces_sampler_returning_a_boolean( sentry_init, traces_sampler_return_value -): - sentry_init(traces_sampler=mock.Mock(return_value=traces_sampler_return_value)) - - transaction = start_transaction(name="dogpark") - assert transaction.sampled is traces_sampler_return_value - - -@pytest.mark.parametrize("traces_sampler_return_value", [True, False]) -def test_tolerates_traces_sampler_returning_a_boolean_span_streaming( - sentry_init, traces_sampler_return_value ): sentry_init( traces_sampler=mock.Mock(return_value=traces_sampler_return_value), @@ -198,25 +127,6 @@ def test_tolerates_traces_sampler_returning_a_boolean_span_streaming( assert span.sampled is traces_sampler_return_value -@pytest.mark.parametrize( - "traces_sample_rate,expected_decision", - [(0.0, False), (0.25, False), (0.75, True), (1.00, True)], -) -def test_traces_sampler_raising_falls_back_to_traces_sample_rate( - sentry_init, - traces_sample_rate, - expected_decision, -): - sentry_init( - traces_sampler=mock.Mock(side_effect=ValueError("boom")), - traces_sample_rate=traces_sample_rate, - ) - - baggage = Baggage(sentry_items={"sample_rand": "0.500000"}) - transaction = start_transaction(name="dogpark", baggage=baggage) - assert transaction.sampled is expected_decision - - @pytest.mark.parametrize("parent_sampling_decision", [True, False]) def test_traces_sampler_raising_falls_back_to_parent_sampling_decision( sentry_init, parent_sampling_decision @@ -226,20 +136,24 @@ def test_traces_sampler_raising_falls_back_to_parent_sampling_decision( sentry_init( traces_sampler=mock.Mock(side_effect=ValueError("boom")), traces_sample_rate=0.0 if parent_sampling_decision else 1.0, + trace_lifecycle="stream", ) - baggage = Baggage(sentry_items={"sample_rand": "0.500000"}) - transaction = start_transaction( - name="dogpark", baggage=baggage, parent_sampled=parent_sampling_decision + sentry_sdk.traces.continue_trace( + { + "sentry-trace": f"0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-{int(parent_sampling_decision)}" + } ) - assert transaction.sampled is parent_sampling_decision + + with sentry_sdk.traces.start_span(name="dogpark") as span: + assert span.sampled is parent_sampling_decision @pytest.mark.parametrize( "traces_sample_rate,expected_decision", [(0.0, False), (0.25, False), (0.75, True), (1.00, True)], ) -def test_traces_sampler_raising_falls_back_to_traces_sample_rate_span_streaming( +def test_traces_sampler_raising_falls_back_to_traces_sample_rate( sentry_init, traces_sample_rate, expected_decision, @@ -265,7 +179,7 @@ def test_traces_sampler_raising_falls_back_to_traces_sample_rate_span_streaming( "traces_sample_rate,expected_decision", [(0.0, False), (0.25, False), (0.75, True), (1.00, True)], ) -def test_traces_sampler_raising_no_incoming_trace_falls_back_to_traces_sample_rate_span_streaming( +def test_traces_sampler_raising_no_incoming_trace_falls_back_to_traces_sample_rate( sentry_init, traces_sample_rate, expected_decision, @@ -288,17 +202,6 @@ def test_traces_sampler_raising_no_incoming_trace_falls_back_to_traces_sample_ra def test_traces_sampler_raising_no_incoming_trace_and_no_traces_sample_rate( sentry_init, -): - sentry_init( - traces_sampler=mock.Mock(side_effect=ValueError("boom")), - ) - - transaction = start_transaction(name="dogpark") - assert transaction.sampled is False - - -def test_traces_sampler_raising_no_incoming_trace_and_no_traces_sample_rate_span_streaming( - sentry_init, ): sentry_init( traces_sampler=mock.Mock(side_effect=ValueError("boom")), @@ -310,20 +213,7 @@ def test_traces_sampler_raising_no_incoming_trace_and_no_traces_sample_rate_span @pytest.mark.parametrize("sampling_decision", [True, False]) -def test_only_captures_transaction_when_sampled_is_true( - sentry_init, sampling_decision, capture_events -): - sentry_init(traces_sampler=mock.Mock(return_value=sampling_decision)) - events = capture_events() - - transaction = start_transaction(name="dogpark") - transaction.finish() - - assert len(events) == (1 if sampling_decision else 0) - - -@pytest.mark.parametrize("sampling_decision", [True, False]) -def test_only_captures_segment_when_sampled_is_true_span_streaming( +def test_only_captures_segment_when_sampled_is_true( sentry_init, sampling_decision, capture_items ): sentry_init( @@ -349,27 +239,6 @@ def test_prefers_traces_sampler_to_traces_sample_rate( sentry_init, traces_sample_rate, traces_sampler_return_value, -): - # make traces_sample_rate imply the opposite of traces_sampler, to prove - # that traces_sampler takes precedence - traces_sampler = mock.Mock(return_value=traces_sampler_return_value) - sentry_init( - traces_sample_rate=traces_sample_rate, - traces_sampler=traces_sampler, - ) - - transaction = start_transaction(name="dogpark") - assert traces_sampler.called is True - assert transaction.sampled is traces_sampler_return_value - - -@pytest.mark.parametrize( - "traces_sample_rate,traces_sampler_return_value", [(0, True), (1, False)] -) -def test_prefers_traces_sampler_to_traces_sample_rate_span_streaming( - sentry_init, - traces_sample_rate, - traces_sampler_return_value, ): # make traces_sample_rate imply the opposite of traces_sampler, to prove # that traces_sampler takes precedence @@ -385,23 +254,8 @@ def test_prefers_traces_sampler_to_traces_sample_rate_span_streaming( assert span.sampled is traces_sampler_return_value -@pytest.mark.parametrize("parent_sampling_decision", [True, False]) -def test_ignores_inherited_sample_decision_when_traces_sampler_defined( - sentry_init, parent_sampling_decision -): - # make traces_sampler pick the opposite of the inherited decision, to prove - # that traces_sampler takes precedence - traces_sampler = mock.Mock(return_value=not parent_sampling_decision) - sentry_init(traces_sampler=traces_sampler) - - transaction = start_transaction( - name="dogpark", parent_sampled=parent_sampling_decision - ) - assert transaction.sampled is not parent_sampling_decision - - @pytest.mark.parametrize("parent_sampling_decision", ["1", "0"]) -def test_ignores_inherited_sample_decision_when_traces_sampler_defined_span_streaming( +def test_ignores_inherited_sample_decision_when_traces_sampler_defined( sentry_init, parent_sampling_decision ): # make traces_sampler pick the opposite of the inherited decision, to prove @@ -421,38 +275,8 @@ def test_ignores_inherited_sample_decision_when_traces_sampler_defined_span_stre assert span.sampled is not bool(int(parent_sampling_decision)) -@pytest.mark.parametrize("explicit_decision", [True, False]) -def test_traces_sampler_doesnt_overwrite_explicitly_passed_sampling_decision( - sentry_init, explicit_decision -): - # make traces_sampler pick the opposite of the explicit decision, to prove - # that the explicit decision takes precedence - traces_sampler = mock.Mock(return_value=not explicit_decision) - sentry_init(traces_sampler=traces_sampler) - - transaction = start_transaction(name="dogpark", sampled=explicit_decision) - assert transaction.sampled is explicit_decision - - -@pytest.mark.parametrize("parent_sampling_decision", [True, False]) -def test_inherits_parent_sampling_decision_when_traces_sampler_undefined( - sentry_init, parent_sampling_decision -): - # make sure the parent sampling decision is the opposite of what - # traces_sample_rate would produce, to prove the inheritance takes - # precedence - sentry_init(traces_sample_rate=0.5) - mock_random_value = 0.25 if parent_sampling_decision is False else 0.75 - - with mock.patch.object(random, "random", return_value=mock_random_value): - transaction = start_transaction( - name="dogpark", parent_sampled=parent_sampling_decision - ) - assert transaction.sampled is parent_sampling_decision - - @pytest.mark.parametrize("parent_sampling_decision", ["1", "0"]) -def test_inherits_parent_sampling_decision_when_traces_sampler_undefined_span_streaming( +def test_inherits_parent_sampling_decision_when_traces_sampler_undefined( sentry_init, parent_sampling_decision ): sentry_init( @@ -475,48 +299,6 @@ def test_inherits_parent_sampling_decision_when_traces_sampler_undefined_span_st assert span.sampled is bool(int(parent_sampling_decision)) -@pytest.mark.parametrize("parent_sampling_decision", [True, False]) -def test_passes_parent_sampling_decision_in_sampling_context( - sentry_init, parent_sampling_decision -): - sentry_init(traces_sample_rate=1.0) - - sentry_trace_header = ( - "12312012123120121231201212312012-1121201211212012-{sampled}".format( - sampled=int(parent_sampling_decision) - ) - ) - - transaction = sentry_sdk.continue_trace( - {"sentry-trace": sentry_trace_header}, - name="dogpark", - ) - - def mock_set_initial_sampling_decision(_, sampling_context): - assert "parent_sampled" in sampling_context - assert sampling_context["parent_sampled"] is parent_sampling_decision - - with mock.patch( - "sentry_sdk.tracing.Transaction._set_initial_sampling_decision", - mock_set_initial_sampling_decision, - ): - start_transaction(transaction=transaction) - - -def test_passes_custom_sampling_context_from_start_transaction_to_traces_sampler( - sentry_init, - DictionaryContaining, # noqa: N803 -): - traces_sampler = mock.Mock() - sentry_init(traces_sampler=traces_sampler) - - start_transaction(custom_sampling_context={"dogs": "yes", "cats": "maybe"}) - - traces_sampler.assert_any_call( - DictionaryContaining({"dogs": "yes", "cats": "maybe"}) - ) - - def test_custom_sampling_context(sentry_init): class MyClass: ... @@ -599,29 +381,6 @@ def test_warns_and_sets_sampled_to_false_on_invalid_traces_sampler_return_value( sentry_init, traces_sampler_return_value, StringContaining, # noqa: N803 -): - sentry_init(traces_sampler=mock.Mock(return_value=traces_sampler_return_value)) - - with mock.patch.object(logger, "warning", mock.Mock()): - transaction = start_transaction(name="dogpark") - logger.warning.assert_any_call(StringContaining("Given sample rate is invalid")) - assert transaction.sampled is False - - -@pytest.mark.parametrize( - "traces_sampler_return_value", - [ - "dogs are great", # wrong type - None, # wrong type - float("NaN"), # wrong type (edge: float, but not a valid rate) - -1.121, # wrong value - 1.231, # wrong value - ], -) -def test_warns_and_sets_sampled_to_false_on_invalid_traces_sampler_return_value_span_streaming( - sentry_init, - traces_sampler_return_value, - StringContaining, # noqa: N803 ): sentry_init( traces_sampler=mock.Mock(return_value=traces_sampler_return_value), @@ -637,11 +396,11 @@ def test_warns_and_sets_sampled_to_false_on_invalid_traces_sampler_return_value_ @pytest.mark.parametrize( "traces_sample_rate,sampled_output,expected_record_lost_event_calls", [ - (None, False, []), + (None, None, []), ( 0.0, False, - [("sample_rate", "transaction", None, 1), ("sample_rate", "span", None, 1)], + [("sample_rate", "span", None, 1)], ), (1.0, True, []), ], @@ -653,12 +412,12 @@ def test_records_lost_event_only_if_traces_sample_rate_enabled( sampled_output, expected_record_lost_event_calls, ): - sentry_init(traces_sample_rate=traces_sample_rate) + sentry_init(traces_sample_rate=traces_sample_rate, trace_lifecycle="stream") record_lost_event_calls = capture_record_lost_event_calls() - transaction = start_transaction(name="dogpark") - assert transaction.sampled is sampled_output - transaction.finish() + span = sentry_sdk.traces.start_span(name="dogpark") + assert span.sampled is sampled_output + span.end() # Use Counter because order of calls does not matter assert Counter(record_lost_event_calls) == Counter(expected_record_lost_event_calls) @@ -667,11 +426,11 @@ def test_records_lost_event_only_if_traces_sample_rate_enabled( @pytest.mark.parametrize( "traces_sampler,sampled_output,expected_record_lost_event_calls", [ - (None, False, []), + (None, None, []), ( lambda _x: 0.0, False, - [("sample_rate", "transaction", None, 1), ("sample_rate", "span", None, 1)], + [("sample_rate", "span", None, 1)], ), (lambda _x: 1.0, True, []), ], @@ -683,12 +442,12 @@ def test_records_lost_event_only_if_traces_sampler_enabled( sampled_output, expected_record_lost_event_calls, ): - sentry_init(traces_sampler=traces_sampler) + sentry_init(traces_sampler=traces_sampler, trace_lifecycle="stream") record_lost_event_calls = capture_record_lost_event_calls() - transaction = start_transaction(name="dogpark") - assert transaction.sampled is sampled_output - transaction.finish() + segment = sentry_sdk.traces.start_span(name="dogpark") + assert segment.sampled is sampled_output + segment.end() # Use Counter because order of calls does not matter assert Counter(record_lost_event_calls) == Counter(expected_record_lost_event_calls) From d976c514e3f0694b331caf5425dd8295ed617dc3 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 11 Sep 2026 10:16:45 +0200 Subject: [PATCH 5/5] . --- tests/tracing/test_sampling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tracing/test_sampling.py b/tests/tracing/test_sampling.py index cb41296bae..f0b79a8a78 100644 --- a/tests/tracing/test_sampling.py +++ b/tests/tracing/test_sampling.py @@ -61,7 +61,7 @@ def test_get_span_from_scope_regardless_of_sampling_decision( else: # noop spans are not set on the scope unless they're segments assert scope.streamed_span.name == "/" - assert scope.transaction.name == "/" + assert scope._transaction == "/" @pytest.mark.parametrize(