Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,110 @@ 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):
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, 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

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 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.expected_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 actual_child == EmittedSpanMetadataEqual(
expected_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)
actual_root_span = event["contexts"]["trace"]

if actual_root_span != EmittedSpanMetadataEqual(
self.expected_root_span, **self.span_matcher_kwargs
):
return False

return self._subtree_eq(
actual_root_span, self.expected_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():
"""
Expand Down
76 changes: 49 additions & 27 deletions tests/integrations/django/asgi/test_asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
)
Expand Down Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"
)
Expand Down Expand Up @@ -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",
Expand All @@ -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"
)
Expand Down Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"
)
Expand Down Expand Up @@ -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"
)
Expand Down
Loading