Files
2020-04-08 10:39:44 -07:00

388 lines
13 KiB
Python

# -*- coding: utf-8 -*-
import time
import re
from unittest import TestCase
from ddtrace.contrib.flask import TraceMiddleware
from ddtrace.constants import SAMPLING_PRIORITY_KEY
from ddtrace.ext import http, errors
from tests.opentracer.utils import init_tracer
from .web import create_app
from ...test_tracer import get_dummy_tracer
from ...utils import assert_span_http_status_code
class TestFlask(TestCase):
"""Ensures Flask is properly instrumented."""
def setUp(self):
self.tracer = get_dummy_tracer()
self.flask_app = create_app()
self.traced_app = TraceMiddleware(
self.flask_app,
self.tracer,
service='test.flask.service',
distributed_tracing=True,
)
# make the app testable
self.flask_app.config['TESTING'] = True
self.app = self.flask_app.test_client()
def test_double_instrumentation(self):
# ensure Flask is never instrumented twice when `ddtrace-run`
# and `TraceMiddleware` are used together. `traced_app` MUST
# be assigned otherwise it's not possible to reproduce the
# problem (the test scope must keep a strong reference)
traced_app = TraceMiddleware(self.flask_app, self.tracer) # noqa: F841
rv = self.app.get('/child')
assert rv.status_code == 200
spans = self.tracer.writer.pop()
assert len(spans) == 2
def test_double_instrumentation_config(self):
# ensure Flask uses the last set configuration to be sure
# there are no breaking changes for who uses `ddtrace-run`
# with the `TraceMiddleware`
TraceMiddleware(
self.flask_app,
self.tracer,
service='new-intake',
distributed_tracing=False,
)
assert self.flask_app._service == 'new-intake'
assert self.flask_app._use_distributed_tracing is False
rv = self.app.get('/child')
assert rv.status_code == 200
spans = self.tracer.writer.pop()
assert len(spans) == 2
def test_child(self):
start = time.time()
rv = self.app.get('/child')
end = time.time()
# ensure request worked
assert rv.status_code == 200
assert rv.data == b'child'
# ensure trace worked
spans = self.tracer.writer.pop()
assert len(spans) == 2
spans_by_name = {s.name: s for s in spans}
s = spans_by_name['flask.request']
assert s.span_id
assert s.trace_id
assert not s.parent_id
assert s.service == 'test.flask.service'
assert s.resource == 'child'
assert s.start >= start
assert s.duration <= end - start
assert s.error == 0
c = spans_by_name['child']
assert c.span_id
assert c.trace_id == s.trace_id
assert c.parent_id == s.span_id
assert c.service == 'test.flask.service'
assert c.resource == 'child'
assert c.start >= start
assert c.duration <= end - start
assert c.error == 0
def test_success(self):
start = time.time()
rv = self.app.get('/')
end = time.time()
# ensure request worked
assert rv.status_code == 200
assert rv.data == b'hello'
# ensure trace worked
assert not self.tracer.current_span(), self.tracer.current_span().pprint()
spans = self.tracer.writer.pop()
assert len(spans) == 1
s = spans[0]
assert s.service == 'test.flask.service'
assert s.resource == 'index'
assert s.start >= start
assert s.duration <= end - start
assert s.error == 0
assert_span_http_status_code(s, 200)
assert s.meta.get(http.METHOD) == 'GET'
services = self.tracer.writer.pop_services()
expected = {}
assert services == expected
def test_template(self):
start = time.time()
rv = self.app.get('/tmpl')
end = time.time()
# ensure request worked
assert rv.status_code == 200
assert rv.data == b'hello earth'
# ensure trace worked
assert not self.tracer.current_span(), self.tracer.current_span().pprint()
spans = self.tracer.writer.pop()
assert len(spans) == 2
by_name = {s.name: s for s in spans}
s = by_name['flask.request']
assert s.service == 'test.flask.service'
assert s.resource == 'tmpl'
assert s.start >= start
assert s.duration <= end - start
assert s.error == 0
assert_span_http_status_code(s, 200)
assert s.meta.get(http.METHOD) == 'GET'
t = by_name['flask.template']
assert t.get_tag('flask.template') == 'test.html'
assert t.parent_id == s.span_id
assert t.trace_id == s.trace_id
assert s.start < t.start < t.start + t.duration < end
def test_handleme(self):
start = time.time()
rv = self.app.get('/handleme')
end = time.time()
# ensure request worked
assert rv.status_code == 202
assert rv.data == b'handled'
# ensure trace worked
assert not self.tracer.current_span(), self.tracer.current_span().pprint()
spans = self.tracer.writer.pop()
assert len(spans) == 1
s = spans[0]
assert s.service == 'test.flask.service'
assert s.resource == 'handle_me'
assert s.start >= start
assert s.duration <= end - start
assert s.error == 0
assert_span_http_status_code(s, 202)
assert s.meta.get(http.METHOD) == 'GET'
def test_template_err(self):
start = time.time()
try:
self.app.get('/tmpl/err')
except Exception:
pass
else:
assert 0
end = time.time()
# ensure trace worked
assert not self.tracer.current_span(), self.tracer.current_span().pprint()
spans = self.tracer.writer.pop()
assert len(spans) == 1
by_name = {s.name: s for s in spans}
s = by_name['flask.request']
assert s.service == 'test.flask.service'
assert s.resource == 'tmpl_err'
assert s.start >= start
assert s.duration <= end - start
assert s.error == 1
assert_span_http_status_code(s, 500)
assert s.meta.get(http.METHOD) == 'GET'
def test_template_render_err(self):
start = time.time()
try:
self.app.get('/tmpl/render_err')
except Exception:
pass
else:
assert 0
end = time.time()
# ensure trace worked
assert not self.tracer.current_span(), self.tracer.current_span().pprint()
spans = self.tracer.writer.pop()
assert len(spans) == 2
by_name = {s.name: s for s in spans}
s = by_name['flask.request']
assert s.service == 'test.flask.service'
assert s.resource == 'tmpl_render_err'
assert s.start >= start
assert s.duration <= end - start
assert s.error == 1
assert_span_http_status_code(s, 500)
assert s.meta.get(http.METHOD) == 'GET'
t = by_name['flask.template']
assert t.get_tag('flask.template') == 'render_err.html'
assert t.error == 1
assert t.parent_id == s.span_id
assert t.trace_id == s.trace_id
def test_error(self):
start = time.time()
rv = self.app.get('/error')
end = time.time()
# ensure the request itself worked
assert rv.status_code == 500
assert rv.data == b'error'
# ensure the request was traced.
assert not self.tracer.current_span()
spans = self.tracer.writer.pop()
assert len(spans) == 1
s = spans[0]
assert s.service == 'test.flask.service'
assert s.resource == 'error'
assert s.start >= start
assert s.duration <= end - start
assert_span_http_status_code(s, 500)
assert s.meta.get(http.METHOD) == 'GET'
def test_fatal(self):
if not self.traced_app.use_signals:
return
start = time.time()
try:
self.app.get('/fatal')
except ZeroDivisionError:
pass
else:
assert 0
end = time.time()
# ensure the request was traced.
assert not self.tracer.current_span()
spans = self.tracer.writer.pop()
assert len(spans) == 1
s = spans[0]
assert s.service == 'test.flask.service'
assert s.resource == 'fatal'
assert s.start >= start
assert s.duration <= end - start
assert_span_http_status_code(s, 500)
assert s.meta.get(http.METHOD) == 'GET'
assert 'ZeroDivisionError' in s.meta.get(errors.ERROR_TYPE), s.meta
assert 'by zero' in s.meta.get(errors.ERROR_MSG)
assert re.search('File ".*/contrib/flask/web.py", line [0-9]+, in fatal', s.meta.get(errors.ERROR_STACK))
def test_unicode(self):
start = time.time()
rv = self.app.get(u'/üŋïĉóđē')
end = time.time()
# ensure request worked
assert rv.status_code == 200
assert rv.data == b'\xc3\xbc\xc5\x8b\xc3\xaf\xc4\x89\xc3\xb3\xc4\x91\xc4\x93'
# ensure trace worked
assert not self.tracer.current_span(), self.tracer.current_span().pprint()
spans = self.tracer.writer.pop()
assert len(spans) == 1
s = spans[0]
assert s.service == 'test.flask.service'
assert s.resource == u'üŋïĉóđē'
assert s.start >= start
assert s.duration <= end - start
assert s.error == 0
assert_span_http_status_code(s, 200)
assert s.meta.get(http.METHOD) == 'GET'
assert s.meta.get(http.URL) == u'http://localhost/üŋïĉóđē'
def test_404(self):
start = time.time()
rv = self.app.get(u'/404/üŋïĉóđē')
end = time.time()
# ensure that we hit a 404
assert rv.status_code == 404
# ensure trace worked
assert not self.tracer.current_span(), self.tracer.current_span().pprint()
spans = self.tracer.writer.pop()
assert len(spans) == 1
s = spans[0]
assert s.service == 'test.flask.service'
assert s.resource == u'404'
assert s.start >= start
assert s.duration <= end - start
assert s.error == 0
assert_span_http_status_code(s, 404)
assert s.meta.get(http.METHOD) == 'GET'
assert s.meta.get(http.URL) == u'http://localhost/404/üŋïĉóđē'
def test_propagation(self):
rv = self.app.get('/', headers={
'x-datadog-trace-id': '1234',
'x-datadog-parent-id': '4567',
'x-datadog-sampling-priority': '2'
})
# ensure request worked
assert rv.status_code == 200
assert rv.data == b'hello'
# ensure trace worked
assert not self.tracer.current_span(), self.tracer.current_span().pprint()
spans = self.tracer.writer.pop()
assert len(spans) == 1
s = spans[0]
# ensure the propagation worked well
assert s.trace_id == 1234
assert s.parent_id == 4567
assert s.get_metric(SAMPLING_PRIORITY_KEY) == 2
def test_custom_span(self):
rv = self.app.get('/custom_span')
assert rv.status_code == 200
# ensure trace worked
assert not self.tracer.current_span(), self.tracer.current_span().pprint()
spans = self.tracer.writer.pop()
assert len(spans) == 1
s = spans[0]
assert s.service == 'test.flask.service'
assert s.resource == 'overridden'
assert s.error == 0
assert_span_http_status_code(s, 200)
assert s.meta.get(http.METHOD) == 'GET'
def test_success_200_ot(self):
"""OpenTracing version of test_success_200."""
ot_tracer = init_tracer('my_svc', self.tracer)
writer = self.tracer.writer
with ot_tracer.start_active_span('ot_span'):
start = time.time()
rv = self.app.get('/')
end = time.time()
# ensure request worked
assert rv.status_code == 200
assert rv.data == b'hello'
# ensure trace worked
assert not self.tracer.current_span(), self.tracer.current_span().pprint()
spans = writer.pop()
assert len(spans) == 2
ot_span, dd_span = spans
# confirm the parenting
assert ot_span.parent_id is None
assert dd_span.parent_id == ot_span.span_id
assert ot_span.resource == 'ot_span'
assert ot_span.service == 'my_svc'
assert dd_span.resource == 'index'
assert dd_span.start >= start
assert dd_span.duration <= end - start
assert dd_span.error == 0
assert_span_http_status_code(dd_span, 200)
assert dd_span.meta.get(http.METHOD) == 'GET'