Skip to content

Commit ed6732c

Browse files
committed
add test file
1 parent bc47eac commit ed6732c

1 file changed

Lines changed: 247 additions & 0 deletions

File tree

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# Copyright The OpenTelemetry Authors
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from sys import modules
5+
from unittest.mock import patch
6+
7+
from django import VERSION
8+
from django.conf import settings
9+
from django.http import HttpResponse
10+
from django.test.client import Client
11+
from django.test.utils import setup_test_environment, teardown_test_environment
12+
from django.utils.deprecation import MiddlewareMixin
13+
14+
from opentelemetry.instrumentation._semconv import (
15+
OTEL_SEMCONV_STABILITY_OPT_IN,
16+
_OpenTelemetrySemanticConventionStability,
17+
)
18+
from opentelemetry.instrumentation.django import DjangoInstrumentor
19+
from opentelemetry.test.wsgitestutil import WsgiTestBase
20+
from opentelemetry.trace import SpanKind
21+
22+
DJANGO_2_0 = VERSION >= (2, 0)
23+
24+
if DJANGO_2_0:
25+
from django.urls import re_path
26+
else:
27+
from django.conf.urls import url as re_path
28+
29+
30+
def traced_view(request):
31+
return HttpResponse()
32+
33+
34+
class MixinMiddleware(MiddlewareMixin):
35+
def process_request(self, request):
36+
pass
37+
38+
39+
class NewStyleMiddleware:
40+
def __init__(self, get_response):
41+
self.get_response = get_response
42+
43+
def __call__(self, request):
44+
return self.get_response(request)
45+
46+
47+
def function_middleware(get_response):
48+
def middleware(request):
49+
return get_response(request)
50+
51+
return middleware
52+
53+
54+
class ErrorMiddleware(MiddlewareMixin):
55+
def process_request(self, request):
56+
raise RuntimeError("middleware error")
57+
58+
59+
urlpatterns = [
60+
re_path(r"^traced/", traced_view),
61+
]
62+
63+
_django_instrumentor = DjangoInstrumentor()
64+
_THIS_MODULE = modules[__name__].__name__
65+
66+
_TEST_MIDDLEWARE = [
67+
f"{_THIS_MODULE}.MixinMiddleware",
68+
f"{_THIS_MODULE}.NewStyleMiddleware",
69+
f"{_THIS_MODULE}.function_middleware",
70+
]
71+
72+
73+
class TestMiddlewareSpans(WsgiTestBase):
74+
@classmethod
75+
def setUpClass(cls):
76+
if not settings.configured:
77+
settings.configure(ROOT_URLCONF=modules[__name__])
78+
super().setUpClass()
79+
80+
def setUp(self):
81+
super().setUp()
82+
setup_test_environment()
83+
self._original_middleware = list(
84+
getattr(settings, "MIDDLEWARE", [])
85+
)
86+
self._original_root_urlconf = getattr(
87+
settings, "ROOT_URLCONF", None
88+
)
89+
settings.MIDDLEWARE = list(_TEST_MIDDLEWARE)
90+
settings.ROOT_URLCONF = modules[__name__]
91+
self.env_patch = patch.dict(
92+
"os.environ",
93+
{OTEL_SEMCONV_STABILITY_OPT_IN: "default"},
94+
)
95+
_OpenTelemetrySemanticConventionStability._initialized = False
96+
self.env_patch.start()
97+
_django_instrumentor.instrument(is_middleware_spans_enabled=True)
98+
99+
def tearDown(self):
100+
super().tearDown()
101+
self.env_patch.stop()
102+
teardown_test_environment()
103+
_django_instrumentor.uninstrument()
104+
settings.MIDDLEWARE = self._original_middleware
105+
if self._original_root_urlconf is not None:
106+
settings.ROOT_URLCONF = self._original_root_urlconf
107+
108+
def _get_spans_by_kind(self):
109+
spans = self.memory_exporter.get_finished_spans()
110+
server_spans = [s for s in spans if s.kind == SpanKind.SERVER]
111+
mw_spans = [s for s in spans if s.kind == SpanKind.INTERNAL]
112+
return server_spans, mw_spans
113+
114+
def test_all_categories_create_spans(self):
115+
Client().get("/traced/")
116+
server_spans, mw_spans = self._get_spans_by_kind()
117+
118+
self.assertEqual(len(server_spans), 1)
119+
self.assertEqual(len(mw_spans), 3)
120+
121+
mw_names = {s.name for s in mw_spans}
122+
self.assertIn("django.middleware MixinMiddleware", mw_names)
123+
self.assertIn("django.middleware NewStyleMiddleware", mw_names)
124+
self.assertIn("django.middleware function_middleware", mw_names)
125+
126+
def test_mixin_middleware_creates_span(self):
127+
Client().get("/traced/")
128+
_, mw_spans = self._get_spans_by_kind()
129+
130+
mixin_spans = [
131+
s
132+
for s in mw_spans
133+
if s.name == "django.middleware MixinMiddleware"
134+
]
135+
self.assertEqual(len(mixin_spans), 1)
136+
137+
def test_newstyle_middleware_creates_span(self):
138+
Client().get("/traced/")
139+
_, mw_spans = self._get_spans_by_kind()
140+
141+
newstyle_spans = [
142+
s
143+
for s in mw_spans
144+
if s.name == "django.middleware NewStyleMiddleware"
145+
]
146+
self.assertEqual(len(newstyle_spans), 1)
147+
148+
def test_function_middleware_creates_span(self):
149+
Client().get("/traced/")
150+
_, mw_spans = self._get_spans_by_kind()
151+
152+
func_spans = [
153+
s
154+
for s in mw_spans
155+
if s.name == "django.middleware function_middleware"
156+
]
157+
self.assertEqual(len(func_spans), 1)
158+
159+
# -- Span properties --
160+
161+
def test_span_kind_is_internal(self):
162+
Client().get("/traced/")
163+
_, mw_spans = self._get_spans_by_kind()
164+
165+
for span in mw_spans:
166+
self.assertEqual(span.kind, SpanKind.INTERNAL)
167+
168+
def test_spans_share_trace_with_server_span(self):
169+
Client().get("/traced/")
170+
server_spans, mw_spans = self._get_spans_by_kind()
171+
172+
self.assertEqual(len(server_spans), 1)
173+
server_trace_id = server_spans[0].get_span_context().trace_id
174+
175+
for mw_span in mw_spans:
176+
self.assertEqual(
177+
mw_span.get_span_context().trace_id,
178+
server_trace_id,
179+
)
180+
181+
def test_span_naming_format(self):
182+
Client().get("/traced/")
183+
_, mw_spans = self._get_spans_by_kind()
184+
185+
for span in mw_spans:
186+
self.assertTrue(
187+
span.name.startswith("django.middleware "),
188+
f"Span name '{span.name}' does not match expected format",
189+
)
190+
191+
# -- Edge cases --
192+
193+
def test_disabled_by_default(self):
194+
_django_instrumentor.uninstrument()
195+
_django_instrumentor.instrument()
196+
197+
Client().get("/traced/")
198+
_, mw_spans = self._get_spans_by_kind()
199+
self.assertEqual(len(mw_spans), 0)
200+
201+
def test_uninstrument_removes_spans(self):
202+
_django_instrumentor.uninstrument()
203+
_django_instrumentor.instrument()
204+
205+
Client().get("/traced/")
206+
_, mw_spans = self._get_spans_by_kind()
207+
self.assertEqual(len(mw_spans), 0)
208+
209+
def _reinstrument_with_middleware(self, middleware_list, **kwargs):
210+
_django_instrumentor.uninstrument()
211+
settings.MIDDLEWARE = middleware_list
212+
kwargs.setdefault("is_middleware_spans_enabled", True)
213+
_django_instrumentor.instrument(**kwargs)
214+
215+
def test_middleware_error_creates_span(self):
216+
self._reinstrument_with_middleware(
217+
[f"{_THIS_MODULE}.ErrorMiddleware"]
218+
)
219+
220+
try:
221+
Client().get("/traced/")
222+
except RuntimeError:
223+
pass
224+
225+
spans = self.memory_exporter.get_finished_spans()
226+
mw_spans = [s for s in spans if s.kind == SpanKind.INTERNAL]
227+
self.assertEqual(len(mw_spans), 1)
228+
self.assertEqual(
229+
mw_spans[0].name, "django.middleware ErrorMiddleware"
230+
)
231+
232+
def test_respects_middleware_position(self):
233+
self._reinstrument_with_middleware(
234+
[
235+
f"{_THIS_MODULE}.MixinMiddleware",
236+
f"{_THIS_MODULE}.NewStyleMiddleware",
237+
],
238+
middleware_position=1,
239+
)
240+
241+
Client().get("/traced/")
242+
spans = self.memory_exporter.get_finished_spans()
243+
mw_spans = [s for s in spans if s.kind == SpanKind.INTERNAL]
244+
245+
mw_names = {s.name for s in mw_spans}
246+
self.assertNotIn("django.middleware MixinMiddleware", mw_names)
247+
self.assertIn("django.middleware NewStyleMiddleware", mw_names)

0 commit comments

Comments
 (0)