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

159 lines
4.7 KiB
Python

import threading
from tornado import httpclient
from tornado.testing import gen_test
from ddtrace.contrib.tornado import patch, unpatch
from ddtrace.ext import http
from . import web
from .web.app import CustomDefaultHandler
from .utils import TornadoTestCase
class TestAsyncConcurrency(TornadoTestCase):
"""
Ensure that application instrumentation doesn't break asynchronous concurrency.
"""
@gen_test
def test_concurrent_requests(self):
REQUESTS_NUMBER = 25
responses = []
# the application must handle concurrent calls
def make_requests():
# use a blocking HTTP client (we're in another thread)
http_client = httpclient.HTTPClient()
url = self.get_url('/nested/')
response = http_client.fetch(url)
responses.append(response)
assert 200 == response.code
assert 'OK' == response.body.decode('utf-8')
# freeing file descriptors
http_client.close()
# blocking call executed in different threads
threads = [threading.Thread(target=make_requests) for _ in range(REQUESTS_NUMBER)]
for t in threads:
t.start()
while len(responses) < REQUESTS_NUMBER:
yield web.compat.sleep(0.001)
for t in threads:
t.join()
# the trace is created
traces = self.tracer.writer.pop_traces()
assert REQUESTS_NUMBER == len(traces)
assert 2 == len(traces[0])
class TestAppSafety(TornadoTestCase):
"""
Ensure that the application patch has the proper safety guards.
"""
def test_trace_unpatch(self):
# the application must not be traced if unpatch() is called
patch()
unpatch()
response = self.fetch('/success/')
assert 200 == response.code
traces = self.tracer.writer.pop_traces()
assert 0 == len(traces)
def test_trace_unpatch_not_traced(self):
# the untrace must be safe if the app is not traced
unpatch()
unpatch()
response = self.fetch('/success/')
assert 200 == response.code
traces = self.tracer.writer.pop_traces()
assert 0 == len(traces)
def test_trace_app_twice(self):
# the application must not be traced multiple times
patch()
patch()
response = self.fetch('/success/')
assert 200 == response.code
traces = self.tracer.writer.pop_traces()
assert 1 == len(traces)
assert 1 == len(traces[0])
def test_arbitrary_resource_querystring(self):
# users inputs should not determine `span.resource` field
response = self.fetch('/success/?magic_number=42')
assert 200 == response.code
traces = self.tracer.writer.pop_traces()
assert 1 == len(traces)
assert 1 == len(traces[0])
request_span = traces[0][0]
assert 'tests.contrib.tornado.web.app.SuccessHandler' == request_span.resource
assert self.get_url('/success/') == request_span.get_tag(http.URL)
def test_arbitrary_resource_404(self):
# users inputs should not determine `span.resource` field
response = self.fetch('/does_not_exist/')
assert 404 == response.code
traces = self.tracer.writer.pop_traces()
assert 1 == len(traces)
assert 1 == len(traces[0])
request_span = traces[0][0]
assert 'tornado.web.ErrorHandler' == request_span.resource
assert self.get_url('/does_not_exist/') == request_span.get_tag(http.URL)
@gen_test
def test_futures_without_context(self):
# ensures that if futures propagation is available, an empty
# context doesn't crash the system
from .web.compat import ThreadPoolExecutor
def job():
with self.tracer.trace('job'):
return 42
executor = ThreadPoolExecutor(max_workers=3)
yield executor.submit(job)
traces = self.tracer.writer.pop_traces()
assert 1 == len(traces)
assert 1 == len(traces[0])
# this trace yields the execution of the thread
span = traces[0][0]
assert 'job' == span.name
class TestCustomAppSafety(TornadoTestCase):
"""
Ensure that the application patch has the proper safety guards,
even for custom default handlers.
"""
def get_settings(self):
return {
'default_handler_class': CustomDefaultHandler,
'default_handler_args': dict(status_code=400),
}
def test_trace_unpatch(self):
# the application must not be traced if unpatch() is called
unpatch()
response = self.fetch('/custom_handler/')
assert 400 == response.code
traces = self.tracer.writer.pop_traces()
assert 0 == len(traces)