From 410751a190196944d0ab4d730b3142827abf998c Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 16 Oct 2025 14:17:11 +0200 Subject: [PATCH 1/5] tests: Remove forked from Django tests --- tests/conftest.py | 103 ++++++++++++++++++ tests/integrations/django/asgi/test_asgi.py | 76 ++++++++----- tests/integrations/django/test_basic.py | 13 --- .../integrations/django/test_cache_module.py | 13 --- .../django/test_data_scrubbing.py | 3 - .../integrations/django/test_db_query_data.py | 10 -- 6 files changed, 152 insertions(+), 66 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index faa0251d9b..a58ea10618 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -389,6 +389,109 @@ def render_span(span): return inner +@pytest.fixture(name="EmittedSpanMetadataEqual") +def span_metadata_matcher(): + class EmittedSpanMetadataEqual: + def __init__( + self, + span, + check_trace_id=True, + check_op=True, + check_status=True, + check_origin=True, + check_name=True, + ): + self.span = span + + self.check_trace_id = check_trace_id + self.check_op = check_op + self.check_status = check_status + self.check_origin = check_origin + self.check_name = check_name + + def __eq__(self, span): + print("OP", self.span["op"], span.op) + return ( + (not self.check_trace_id or self.span["trace_id"] == span.trace_id) + and (not self.check_op or self.span["op"] == span.op) + and (not self.check_status or self.span["status"] == span.status) + and (not self.check_origin or self.span["origin"] == span.origin) + and ( + not self.check_name or self.span["description"] == span.description + ) + ) + + def __ne__(self, test_string): + return not self.__eq__(test_string) + + return EmittedSpanMetadataEqual + + +@pytest.fixture(name="SpanTreeEqualUnorderedSiblings") +def unordered_siblings_span_tree_matcher(EmittedSpanMetadataEqual): + class SpanTreeEqualUnorderedSiblings: + def __init__(self, root_span, span_tree, **kwargs): + self.root_span = root_span + self.span_tree = span_tree + + self.span_matcher_kwargs = kwargs + + def _construct_parent_to_spans_mapping(self, event): + by_parent = {} + for span in event["spans"]: + by_parent.setdefault(span["parent_span_id"], []).append(span) + + return by_parent + + def _subtree_eq( + self, actual_subtree_root_span, expected_subtree_root_span, by_parent + ): + if actual_subtree_root_span["span_id"] not in by_parent: + return expected_subtree_root_span == EmittedSpanMetadataEqual( + actual_subtree_root_span, **self.span_matcher_kwargs + ) + + actual_span_children = by_parent[actual_subtree_root_span["span_id"]] + expected_span_children = self.span_tree[expected_subtree_root_span] + + if len(actual_span_children) != len(expected_span_children): + return False + + for expected_child in expected_span_children: + found = False + for actual_child in actual_span_children: + if expected_child == EmittedSpanMetadataEqual( + actual_child, **self.span_matcher_kwargs + ): + found = True + if not self._subtree_eq( + actual_child, expected_child, by_parent + ): + return False + continue + + if not found: + return False + + return True + + def __eq__(self, event): + by_parent = self._construct_parent_to_spans_mapping(event) + root_span = event["contexts"]["trace"] + + if self.root_span != EmittedSpanMetadataEqual( + root_span, **self.span_matcher_kwargs + ): + return False + + return self._subtree_eq(root_span, self.root_span, by_parent) + + def __ne__(self, test_string): + return not self.__eq__(test_string) + + return SpanTreeEqualUnorderedSiblings + + @pytest.fixture(name="StringContaining") def string_containing_matcher(): """ diff --git a/tests/integrations/django/asgi/test_asgi.py b/tests/integrations/django/asgi/test_asgi.py index 8a30c7f5c0..ec4247805f 100644 --- a/tests/integrations/django/asgi/test_asgi.py +++ b/tests/integrations/django/asgi/test_asgi.py @@ -10,6 +10,7 @@ import pytest from channels.testing import HttpCommunicator from sentry_sdk import capture_message +from sentry_sdk.tracing import Span from sentry_sdk.integrations.django import DjangoIntegration from sentry_sdk.integrations.django.asgi import _asgi_middleware_mixin_factory from tests.integrations.django.myapp.asgi import channels_application @@ -29,7 +30,6 @@ @pytest.mark.parametrize("application", APPS) @pytest.mark.asyncio -@pytest.mark.forked @pytest.mark.skipif( django.VERSION < (3, 0), reason="Django ASGI support shipped in 3.0" ) @@ -86,7 +86,6 @@ async def test_basic(sentry_init, capture_events, application): @pytest.mark.parametrize("application", APPS) @pytest.mark.asyncio -@pytest.mark.forked @pytest.mark.skipif( django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1" ) @@ -119,7 +118,6 @@ async def test_async_views(sentry_init, capture_events, application): @pytest.mark.parametrize("application", APPS) @pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"]) @pytest.mark.asyncio -@pytest.mark.forked @pytest.mark.skipif( django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1" ) @@ -165,7 +163,6 @@ async def test_active_thread_id( @pytest.mark.asyncio -@pytest.mark.forked @pytest.mark.skipif( django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1" ) @@ -208,7 +205,6 @@ async def test_async_views_concurrent_execution(sentry_init, settings): @pytest.mark.asyncio -@pytest.mark.forked @pytest.mark.skipif( django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1" ) @@ -255,12 +251,11 @@ async def test_async_middleware_that_is_function_concurrent_execution( @pytest.mark.asyncio -@pytest.mark.forked @pytest.mark.skipif( django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1" ) async def test_async_middleware_spans( - sentry_init, render_span_tree, capture_events, settings + sentry_init, SpanTreeEqualUnorderedSiblings, capture_events, settings ): settings.MIDDLEWARE = [ "django.contrib.sessions.middleware.SessionMiddleware", @@ -286,26 +281,57 @@ async def test_async_middleware_spans( (transaction,) = events - assert ( - render_span_tree(transaction) - == """\ -- op="http.server": description=null - - op="event.django": description="django.db.reset_queries" - - op="event.django": description="django.db.close_old_connections" - - op="middleware.django": description="django.contrib.sessions.middleware.SessionMiddleware.__acall__" - - op="middleware.django": description="django.contrib.auth.middleware.AuthenticationMiddleware.__acall__" - - op="middleware.django": description="django.middleware.csrf.CsrfViewMiddleware.__acall__" - - op="middleware.django": description="tests.integrations.django.myapp.settings.TestMiddleware.__acall__" - - op="middleware.django": description="django.middleware.csrf.CsrfViewMiddleware.process_view" - - op="view.render": description="simple_async_view" - - op="event.django": description="django.db.close_old_connections" - - op="event.django": description="django.core.cache.close_caches" - - op="event.django": description="django.core.handlers.base.reset_urlconf\"""" + http_server_span = Span(op="http.server", name=None) + session_middleware_span = Span( + op="middleware.django", + name="django.contrib.sessions.middleware.SessionMiddleware.__acall__", + ) + authentication_middleware_span = Span( + op="middleware.django", + name="django.contrib.auth.middleware.AuthenticationMiddleware.__acall__", + ) + csrf_view_middleware_span = Span( + op="middleware.django", + name="django.middleware.csrf.CsrfViewMiddleware.__acall__", + ) + test_middleware_span = Span( + op="middleware.django", + name="tests.integrations.django.myapp.settings.TestMiddleware.__acall__", + ) + + span_tree = { + http_server_span: { + session_middleware_span, + Span(op="event.django", name="django.db.reset_queries"), + Span(op="event.django", name="django.db.close_old_connections"), + Span(op="event.django", name="django.db.close_old_connections"), + Span(op="event.django", name="django.core.cache.close_caches"), + Span(op="event.django", name="django.core.handlers.base.reset_urlconf"), + }, + session_middleware_span: {authentication_middleware_span}, + authentication_middleware_span: {csrf_view_middleware_span}, + csrf_view_middleware_span: {test_middleware_span}, + test_middleware_span: { + Span( + op="middleware.django", + name="django.middleware.csrf.CsrfViewMiddleware.process_view", + ), + Span(op="view.render", name="simple_async_view"), + }, + } + + assert transaction == SpanTreeEqualUnorderedSiblings( + http_server_span, + span_tree, + check_trace_id=False, + check_op=True, + check_status=False, + check_origin=False, + check_name=True, ) @pytest.mark.asyncio -@pytest.mark.forked @pytest.mark.skipif( django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1" ) @@ -333,7 +359,6 @@ async def test_has_trace_if_performance_enabled(sentry_init, capture_events): @pytest.mark.asyncio -@pytest.mark.forked @pytest.mark.skipif( django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1" ) @@ -364,7 +389,6 @@ async def test_has_trace_if_performance_disabled(sentry_init, capture_events): @pytest.mark.asyncio -@pytest.mark.forked @pytest.mark.skipif( django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1" ) @@ -398,7 +422,6 @@ async def test_trace_from_headers_if_performance_enabled(sentry_init, capture_ev @pytest.mark.asyncio -@pytest.mark.forked @pytest.mark.skipif( django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1" ) @@ -529,7 +552,6 @@ async def test_trace_from_headers_if_performance_disabled(sentry_init, capture_e ], ) @pytest.mark.asyncio -@pytest.mark.forked @pytest.mark.skipif( django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1" ) diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py index bbe29c7238..e1921af21e 100644 --- a/tests/integrations/django/test_basic.py +++ b/tests/integrations/django/test_basic.py @@ -268,7 +268,6 @@ def test_trace_from_headers_if_performance_disabled( assert error_event["contexts"]["trace"]["trace_id"] == trace_id -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_user_captured(sentry_init, client, capture_events): sentry_init(integrations=[DjangoIntegration()], send_default_pii=True) @@ -290,7 +289,6 @@ def test_user_captured(sentry_init, client, capture_events): } -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_queryset_repr(sentry_init, capture_events): sentry_init(integrations=[DjangoIntegration()]) @@ -313,7 +311,6 @@ def test_queryset_repr(sentry_init, capture_events): ) -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_context_nested_queryset_repr(sentry_init, capture_events): sentry_init(integrations=[DjangoIntegration()]) @@ -363,7 +360,6 @@ def test_500(sentry_init, client): assert content == "Sentry error." -@pytest.mark.forked def test_management_command_raises(): # This just checks for our assumption that Django passes through all # exceptions by default, so our excepthook can be used for management @@ -372,7 +368,6 @@ def test_management_command_raises(): execute_from_command_line(["manage.py", "mycrash"]) -@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.parametrize("with_integration", [True, False]) def test_sql_queries(sentry_init, capture_events, with_integration): @@ -405,7 +400,6 @@ def test_sql_queries(sentry_init, capture_events, with_integration): assert crumb["data"]["db.params"] == [123] -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_sql_dict_query_params(sentry_init, capture_events): sentry_init( @@ -440,7 +434,6 @@ def test_sql_dict_query_params(sentry_init, capture_events): assert crumb["data"]["db.params"] == {"my_foo": 10} -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_response_trace(sentry_init, client, capture_events, render_span_tree): pytest.importorskip("rest_framework") @@ -470,7 +463,6 @@ def test_response_trace(sentry_init, client, capture_events, render_span_tree): lambda sql: sql.SQL('SELECT %(my_param)s FROM "foobar"'), ], ) -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_sql_psycopg2_string_composition(sentry_init, capture_events, query): sentry_init( @@ -502,7 +494,6 @@ def test_sql_psycopg2_string_composition(sentry_init, capture_events, query): assert crumb["data"]["db.params"] == {"my_param": 10} -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_sql_psycopg2_placeholders(sentry_init, capture_events): sentry_init( @@ -562,7 +553,6 @@ def test_sql_psycopg2_placeholders(sentry_init, capture_events): ] -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_django_connect_trace(sentry_init, client, capture_events, render_span_tree): """ @@ -599,7 +589,6 @@ def test_django_connect_trace(sentry_init, client, capture_events, render_span_t assert '- op="db": description="connect"' in render_span_tree(event) -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_django_connect_breadcrumbs(sentry_init, capture_events): """ @@ -635,7 +624,6 @@ def test_django_connect_breadcrumbs(sentry_init, capture_events): ] -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_db_connection_span_data(sentry_init, client, capture_events): sentry_init( @@ -958,7 +946,6 @@ def test_render_spans(sentry_init, client, capture_events, render_span_tree): @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_render_spans_queryset_in_data(sentry_init, client, capture_events): sentry_init( diff --git a/tests/integrations/django/test_cache_module.py b/tests/integrations/django/test_cache_module.py index bc58dd8471..91d1cc20b6 100644 --- a/tests/integrations/django/test_cache_module.py +++ b/tests/integrations/django/test_cache_module.py @@ -90,7 +90,6 @@ def use_django_caching_with_cluster(settings): } -@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") def test_cache_spans_disabled_middleware( @@ -116,7 +115,6 @@ def test_cache_spans_disabled_middleware( assert len(second_event["spans"]) == 0 -@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") def test_cache_spans_disabled_decorator( @@ -142,7 +140,6 @@ def test_cache_spans_disabled_decorator( assert len(second_event["spans"]) == 0 -@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") def test_cache_spans_disabled_templatetag( @@ -168,7 +165,6 @@ def test_cache_spans_disabled_templatetag( assert len(second_event["spans"]) == 0 -@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") def test_cache_spans_middleware( @@ -238,7 +234,6 @@ def test_cache_spans_middleware( assert second_event["spans"][1]["data"]["cache.item_size"] == 58 -@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") def test_cache_spans_decorator(sentry_init, client, capture_events, use_django_caching): @@ -293,7 +288,6 @@ def test_cache_spans_decorator(sentry_init, client, capture_events, use_django_c assert second_event["spans"][1]["data"]["cache.item_size"] == 58 -@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") def test_cache_spans_templatetag( @@ -386,7 +380,6 @@ def test_cache_spans_get_span_description( assert _get_span_description(method_name, args, kwargs) == expected_description -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_cache_spans_location_with_port( sentry_init, client, capture_events, use_django_caching_with_port @@ -414,7 +407,6 @@ def test_cache_spans_location_with_port( assert span["data"]["network.peer.port"] == 6379 -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_cache_spans_location_without_port( sentry_init, client, capture_events, use_django_caching_without_port @@ -440,7 +432,6 @@ def test_cache_spans_location_without_port( assert "network.peer.port" not in span["data"] -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_cache_spans_location_with_cluster( sentry_init, client, capture_events, use_django_caching_with_cluster @@ -467,7 +458,6 @@ def test_cache_spans_location_with_cluster( assert "network.peer.port" not in span["data"].keys() -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_cache_spans_item_size(sentry_init, client, capture_events, use_django_caching): sentry_init( @@ -509,7 +499,6 @@ def test_cache_spans_item_size(sentry_init, client, capture_events, use_django_c assert second_event["spans"][1]["data"]["cache.item_size"] == 58 -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_cache_spans_get_many(sentry_init, capture_events, use_django_caching): sentry_init( @@ -558,7 +547,6 @@ def test_cache_spans_get_many(sentry_init, capture_events, use_django_caching): assert transaction["spans"][6]["description"] == f"S{id + 1}" -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_cache_spans_set_many(sentry_init, capture_events, use_django_caching): sentry_init( @@ -597,7 +585,6 @@ def test_cache_spans_set_many(sentry_init, capture_events, use_django_caching): assert transaction["spans"][3]["description"] == f"S{id}" -@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION <= (1, 11), reason="Requires Django > 1.11") def test_span_origin_cache(sentry_init, client, capture_events, use_django_caching): diff --git a/tests/integrations/django/test_data_scrubbing.py b/tests/integrations/django/test_data_scrubbing.py index 128da9b97e..830034890c 100644 --- a/tests/integrations/django/test_data_scrubbing.py +++ b/tests/integrations/django/test_data_scrubbing.py @@ -18,7 +18,6 @@ def client(): return Client(application) -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_scrub_django_session_cookies_removed( sentry_init, @@ -36,7 +35,6 @@ def test_scrub_django_session_cookies_removed( assert "cookies" not in event["request"] -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_scrub_django_session_cookies_filtered( sentry_init, @@ -58,7 +56,6 @@ def test_scrub_django_session_cookies_filtered( } -@pytest.mark.forked @pytest_mark_django_db_decorator() def test_scrub_django_custom_session_cookies_filtered( sentry_init, diff --git a/tests/integrations/django/test_db_query_data.py b/tests/integrations/django/test_db_query_data.py index 41ad9d5e1c..f6c2bd716e 100644 --- a/tests/integrations/django/test_db_query_data.py +++ b/tests/integrations/django/test_db_query_data.py @@ -29,7 +29,6 @@ def client(): return Client(application) -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_query_source_disabled(sentry_init, client, capture_events): sentry_options = { @@ -67,7 +66,6 @@ def test_query_source_disabled(sentry_init, client, capture_events): raise AssertionError("No db span found") -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) @pytest.mark.parametrize("enable_db_query_source", [None, True]) def test_query_source_enabled( @@ -111,7 +109,6 @@ def test_query_source_enabled( raise AssertionError("No db span found") -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_query_source(sentry_init, client, capture_events): sentry_init( @@ -164,7 +161,6 @@ def test_query_source(sentry_init, client, capture_events): raise AssertionError("No db span found") -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_query_source_with_module_in_search_path(sentry_init, client, capture_events): """ @@ -218,7 +214,6 @@ def test_query_source_with_module_in_search_path(sentry_init, client, capture_ev raise AssertionError("No db span found") -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_query_source_with_in_app_exclude(sentry_init, client, capture_events): sentry_init( @@ -281,7 +276,6 @@ def test_query_source_with_in_app_exclude(sentry_init, client, capture_events): raise AssertionError("No db span found") -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_query_source_with_in_app_include(sentry_init, client, capture_events): sentry_init( @@ -327,7 +321,6 @@ def test_query_source_with_in_app_include(sentry_init, client, capture_events): raise AssertionError("No db span found") -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_no_query_source_if_duration_too_short(sentry_init, client, capture_events): sentry_init( @@ -385,7 +378,6 @@ def __exit__(self, type, value, traceback): raise AssertionError("No db span found") -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_query_source_if_duration_over_threshold(sentry_init, client, capture_events): sentry_init( @@ -458,7 +450,6 @@ def __exit__(self, type, value, traceback): raise AssertionError("No db span found") -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_db_span_origin_execute(sentry_init, client, capture_events): sentry_init( @@ -487,7 +478,6 @@ def test_db_span_origin_execute(sentry_init, client, capture_events): assert span["origin"] == "auto.http.django" -@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_db_span_origin_executemany(sentry_init, client, capture_events): sentry_init( From d3e5ed777e7b1ae62ffffe64fb375e3d1f42ec36 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 16 Oct 2025 14:23:48 +0200 Subject: [PATCH 2/5] revert db query tests --- tests/integrations/django/test_db_query_data.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/integrations/django/test_db_query_data.py b/tests/integrations/django/test_db_query_data.py index f6c2bd716e..41ad9d5e1c 100644 --- a/tests/integrations/django/test_db_query_data.py +++ b/tests/integrations/django/test_db_query_data.py @@ -29,6 +29,7 @@ def client(): return Client(application) +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_query_source_disabled(sentry_init, client, capture_events): sentry_options = { @@ -66,6 +67,7 @@ def test_query_source_disabled(sentry_init, client, capture_events): raise AssertionError("No db span found") +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) @pytest.mark.parametrize("enable_db_query_source", [None, True]) def test_query_source_enabled( @@ -109,6 +111,7 @@ def test_query_source_enabled( raise AssertionError("No db span found") +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_query_source(sentry_init, client, capture_events): sentry_init( @@ -161,6 +164,7 @@ def test_query_source(sentry_init, client, capture_events): raise AssertionError("No db span found") +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_query_source_with_module_in_search_path(sentry_init, client, capture_events): """ @@ -214,6 +218,7 @@ def test_query_source_with_module_in_search_path(sentry_init, client, capture_ev raise AssertionError("No db span found") +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_query_source_with_in_app_exclude(sentry_init, client, capture_events): sentry_init( @@ -276,6 +281,7 @@ def test_query_source_with_in_app_exclude(sentry_init, client, capture_events): raise AssertionError("No db span found") +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_query_source_with_in_app_include(sentry_init, client, capture_events): sentry_init( @@ -321,6 +327,7 @@ def test_query_source_with_in_app_include(sentry_init, client, capture_events): raise AssertionError("No db span found") +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_no_query_source_if_duration_too_short(sentry_init, client, capture_events): sentry_init( @@ -378,6 +385,7 @@ def __exit__(self, type, value, traceback): raise AssertionError("No db span found") +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_query_source_if_duration_over_threshold(sentry_init, client, capture_events): sentry_init( @@ -450,6 +458,7 @@ def __exit__(self, type, value, traceback): raise AssertionError("No db span found") +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_db_span_origin_execute(sentry_init, client, capture_events): sentry_init( @@ -478,6 +487,7 @@ def test_db_span_origin_execute(sentry_init, client, capture_events): assert span["origin"] == "auto.http.django" +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_db_span_origin_executemany(sentry_init, client, capture_events): sentry_init( From 7a104361039bc218500e7d07338e1100ed14e66f Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 16 Oct 2025 14:26:55 +0200 Subject: [PATCH 3/5] revert basic tests --- tests/integrations/django/test_basic.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py index e1921af21e..bbe29c7238 100644 --- a/tests/integrations/django/test_basic.py +++ b/tests/integrations/django/test_basic.py @@ -268,6 +268,7 @@ def test_trace_from_headers_if_performance_disabled( assert error_event["contexts"]["trace"]["trace_id"] == trace_id +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_user_captured(sentry_init, client, capture_events): sentry_init(integrations=[DjangoIntegration()], send_default_pii=True) @@ -289,6 +290,7 @@ def test_user_captured(sentry_init, client, capture_events): } +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_queryset_repr(sentry_init, capture_events): sentry_init(integrations=[DjangoIntegration()]) @@ -311,6 +313,7 @@ def test_queryset_repr(sentry_init, capture_events): ) +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_context_nested_queryset_repr(sentry_init, capture_events): sentry_init(integrations=[DjangoIntegration()]) @@ -360,6 +363,7 @@ def test_500(sentry_init, client): assert content == "Sentry error." +@pytest.mark.forked def test_management_command_raises(): # This just checks for our assumption that Django passes through all # exceptions by default, so our excepthook can be used for management @@ -368,6 +372,7 @@ def test_management_command_raises(): execute_from_command_line(["manage.py", "mycrash"]) +@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.parametrize("with_integration", [True, False]) def test_sql_queries(sentry_init, capture_events, with_integration): @@ -400,6 +405,7 @@ def test_sql_queries(sentry_init, capture_events, with_integration): assert crumb["data"]["db.params"] == [123] +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_sql_dict_query_params(sentry_init, capture_events): sentry_init( @@ -434,6 +440,7 @@ def test_sql_dict_query_params(sentry_init, capture_events): assert crumb["data"]["db.params"] == {"my_foo": 10} +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_response_trace(sentry_init, client, capture_events, render_span_tree): pytest.importorskip("rest_framework") @@ -463,6 +470,7 @@ def test_response_trace(sentry_init, client, capture_events, render_span_tree): lambda sql: sql.SQL('SELECT %(my_param)s FROM "foobar"'), ], ) +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_sql_psycopg2_string_composition(sentry_init, capture_events, query): sentry_init( @@ -494,6 +502,7 @@ def test_sql_psycopg2_string_composition(sentry_init, capture_events, query): assert crumb["data"]["db.params"] == {"my_param": 10} +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_sql_psycopg2_placeholders(sentry_init, capture_events): sentry_init( @@ -553,6 +562,7 @@ def test_sql_psycopg2_placeholders(sentry_init, capture_events): ] +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_django_connect_trace(sentry_init, client, capture_events, render_span_tree): """ @@ -589,6 +599,7 @@ def test_django_connect_trace(sentry_init, client, capture_events, render_span_t assert '- op="db": description="connect"' in render_span_tree(event) +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_django_connect_breadcrumbs(sentry_init, capture_events): """ @@ -624,6 +635,7 @@ def test_django_connect_breadcrumbs(sentry_init, capture_events): ] +@pytest.mark.forked @pytest_mark_django_db_decorator(transaction=True) def test_db_connection_span_data(sentry_init, client, capture_events): sentry_init( @@ -946,6 +958,7 @@ def test_render_spans(sentry_init, client, capture_events, render_span_tree): @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_render_spans_queryset_in_data(sentry_init, client, capture_events): sentry_init( From fff18545a955839c5f11f4f361eff97ffb80f11c Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 16 Oct 2025 14:49:21 +0200 Subject: [PATCH 4/5] . --- tests/integrations/django/test_cache_module.py | 13 +++++++++++++ tests/integrations/django/test_data_scrubbing.py | 3 +++ 2 files changed, 16 insertions(+) diff --git a/tests/integrations/django/test_cache_module.py b/tests/integrations/django/test_cache_module.py index 91d1cc20b6..bc58dd8471 100644 --- a/tests/integrations/django/test_cache_module.py +++ b/tests/integrations/django/test_cache_module.py @@ -90,6 +90,7 @@ def use_django_caching_with_cluster(settings): } +@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") def test_cache_spans_disabled_middleware( @@ -115,6 +116,7 @@ def test_cache_spans_disabled_middleware( assert len(second_event["spans"]) == 0 +@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") def test_cache_spans_disabled_decorator( @@ -140,6 +142,7 @@ def test_cache_spans_disabled_decorator( assert len(second_event["spans"]) == 0 +@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") def test_cache_spans_disabled_templatetag( @@ -165,6 +168,7 @@ def test_cache_spans_disabled_templatetag( assert len(second_event["spans"]) == 0 +@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") def test_cache_spans_middleware( @@ -234,6 +238,7 @@ def test_cache_spans_middleware( assert second_event["spans"][1]["data"]["cache.item_size"] == 58 +@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") def test_cache_spans_decorator(sentry_init, client, capture_events, use_django_caching): @@ -288,6 +293,7 @@ def test_cache_spans_decorator(sentry_init, client, capture_events, use_django_c assert second_event["spans"][1]["data"]["cache.item_size"] == 58 +@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION < (1, 9), reason="Requires Django >= 1.9") def test_cache_spans_templatetag( @@ -380,6 +386,7 @@ def test_cache_spans_get_span_description( assert _get_span_description(method_name, args, kwargs) == expected_description +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_cache_spans_location_with_port( sentry_init, client, capture_events, use_django_caching_with_port @@ -407,6 +414,7 @@ def test_cache_spans_location_with_port( assert span["data"]["network.peer.port"] == 6379 +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_cache_spans_location_without_port( sentry_init, client, capture_events, use_django_caching_without_port @@ -432,6 +440,7 @@ def test_cache_spans_location_without_port( assert "network.peer.port" not in span["data"] +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_cache_spans_location_with_cluster( sentry_init, client, capture_events, use_django_caching_with_cluster @@ -458,6 +467,7 @@ def test_cache_spans_location_with_cluster( assert "network.peer.port" not in span["data"].keys() +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_cache_spans_item_size(sentry_init, client, capture_events, use_django_caching): sentry_init( @@ -499,6 +509,7 @@ def test_cache_spans_item_size(sentry_init, client, capture_events, use_django_c assert second_event["spans"][1]["data"]["cache.item_size"] == 58 +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_cache_spans_get_many(sentry_init, capture_events, use_django_caching): sentry_init( @@ -547,6 +558,7 @@ def test_cache_spans_get_many(sentry_init, capture_events, use_django_caching): assert transaction["spans"][6]["description"] == f"S{id + 1}" +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_cache_spans_set_many(sentry_init, capture_events, use_django_caching): sentry_init( @@ -585,6 +597,7 @@ def test_cache_spans_set_many(sentry_init, capture_events, use_django_caching): assert transaction["spans"][3]["description"] == f"S{id}" +@pytest.mark.forked @pytest_mark_django_db_decorator() @pytest.mark.skipif(DJANGO_VERSION <= (1, 11), reason="Requires Django > 1.11") def test_span_origin_cache(sentry_init, client, capture_events, use_django_caching): diff --git a/tests/integrations/django/test_data_scrubbing.py b/tests/integrations/django/test_data_scrubbing.py index 830034890c..128da9b97e 100644 --- a/tests/integrations/django/test_data_scrubbing.py +++ b/tests/integrations/django/test_data_scrubbing.py @@ -18,6 +18,7 @@ def client(): return Client(application) +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_scrub_django_session_cookies_removed( sentry_init, @@ -35,6 +36,7 @@ def test_scrub_django_session_cookies_removed( assert "cookies" not in event["request"] +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_scrub_django_session_cookies_filtered( sentry_init, @@ -56,6 +58,7 @@ def test_scrub_django_session_cookies_filtered( } +@pytest.mark.forked @pytest_mark_django_db_decorator() def test_scrub_django_custom_session_cookies_filtered( sentry_init, From a29f93420863fba0bf845aa638d230f8968d582a Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Thu, 16 Oct 2025 16:18:57 +0200 Subject: [PATCH 5/5] cleanup helper --- tests/conftest.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a58ea10618..082f029a03 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -410,14 +410,13 @@ def __init__( self.check_name = check_name def __eq__(self, span): - print("OP", self.span["op"], span.op) return ( - (not self.check_trace_id or self.span["trace_id"] == span.trace_id) - and (not self.check_op or self.span["op"] == span.op) - and (not self.check_status or self.span["status"] == span.status) - and (not self.check_origin or self.span["origin"] == span.origin) + (not self.check_trace_id or self.span.trace_id == span["trace_id"]) + and (not self.check_op or self.span.op == span["op"]) + and (not self.check_status or self.span.status == span["status"]) + and (not self.check_origin or self.span.origin == span["origin"]) and ( - not self.check_name or self.span["description"] == span.description + not self.check_name or self.span.description == span["description"] ) ) @@ -430,9 +429,9 @@ def __ne__(self, test_string): @pytest.fixture(name="SpanTreeEqualUnorderedSiblings") def unordered_siblings_span_tree_matcher(EmittedSpanMetadataEqual): class SpanTreeEqualUnorderedSiblings: - def __init__(self, root_span, span_tree, **kwargs): - self.root_span = root_span - self.span_tree = span_tree + def __init__(self, expected_root_span, expected_span_tree, **kwargs): + self.expected_root_span = expected_root_span + self.expected_span_tree = expected_span_tree self.span_matcher_kwargs = kwargs @@ -447,12 +446,12 @@ def _subtree_eq( self, actual_subtree_root_span, expected_subtree_root_span, by_parent ): if actual_subtree_root_span["span_id"] not in by_parent: - return expected_subtree_root_span == EmittedSpanMetadataEqual( - actual_subtree_root_span, **self.span_matcher_kwargs + return actual_subtree_root_span == EmittedSpanMetadataEqual( + expected_subtree_root_span, **self.span_matcher_kwargs ) actual_span_children = by_parent[actual_subtree_root_span["span_id"]] - expected_span_children = self.span_tree[expected_subtree_root_span] + expected_span_children = self.expected_span_tree[expected_subtree_root_span] if len(actual_span_children) != len(expected_span_children): return False @@ -460,8 +459,8 @@ def _subtree_eq( for expected_child in expected_span_children: found = False for actual_child in actual_span_children: - if expected_child == EmittedSpanMetadataEqual( - actual_child, **self.span_matcher_kwargs + if actual_child == EmittedSpanMetadataEqual( + expected_child, **self.span_matcher_kwargs ): found = True if not self._subtree_eq( @@ -477,14 +476,16 @@ def _subtree_eq( def __eq__(self, event): by_parent = self._construct_parent_to_spans_mapping(event) - root_span = event["contexts"]["trace"] + actual_root_span = event["contexts"]["trace"] - if self.root_span != EmittedSpanMetadataEqual( - root_span, **self.span_matcher_kwargs + if actual_root_span != EmittedSpanMetadataEqual( + self.expected_root_span, **self.span_matcher_kwargs ): return False - return self._subtree_eq(root_span, self.root_span, by_parent) + return self._subtree_eq( + actual_root_span, self.expected_root_span, by_parent + ) def __ne__(self, test_string): return not self.__eq__(test_string)