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

193 lines
6.5 KiB
Python

import asyncio
import pytest
from opentracing.scope_managers.asyncio import AsyncioScopeManager
import ddtrace
from ddtrace.opentracer.utils import get_context_provider_for_scope_manager
from tests.contrib.asyncio.utils import AsyncioTestCase, mark_asyncio
from .conftest import ot_tracer_factory # noqa: F401
@pytest.fixture()
def ot_tracer(request, ot_tracer_factory): # noqa: F811
# use the dummy asyncio ot tracer
request.instance.ot_tracer = ot_tracer_factory(
'asyncio_svc',
config={},
scope_manager=AsyncioScopeManager(),
context_provider=ddtrace.contrib.asyncio.context_provider,
)
request.instance.ot_writer = request.instance.ot_tracer._dd_tracer.writer
request.instance.dd_tracer = request.instance.ot_tracer._dd_tracer
@pytest.mark.usefixtures('ot_tracer')
class TestTracerAsyncio(AsyncioTestCase):
def reset(self):
self.ot_writer.pop_traces()
@mark_asyncio
def test_trace_coroutine(self):
# it should use the task context when invoked in a coroutine
with self.ot_tracer.start_span('coroutine'):
pass
traces = self.ot_writer.pop_traces()
assert len(traces) == 1
assert len(traces[0]) == 1
assert traces[0][0].name == 'coroutine'
@mark_asyncio
def test_trace_multiple_coroutines(self):
# if multiple coroutines have nested tracing, they must belong
# to the same trace
@asyncio.coroutine
def coro():
# another traced coroutine
with self.ot_tracer.start_active_span('coroutine_2'):
return 42
with self.ot_tracer.start_active_span('coroutine_1'):
value = yield from coro()
# the coroutine has been called correctly
assert value == 42
# a single trace has been properly reported
traces = self.ot_writer.pop_traces()
assert len(traces) == 1
assert len(traces[0]) == 2
assert traces[0][0].name == 'coroutine_1'
assert traces[0][1].name == 'coroutine_2'
# the parenting is correct
assert traces[0][0] == traces[0][1]._parent
assert traces[0][0].trace_id == traces[0][1].trace_id
@mark_asyncio
def test_exception(self):
@asyncio.coroutine
def f1():
with self.ot_tracer.start_span('f1'):
raise Exception('f1 error')
with pytest.raises(Exception):
yield from f1()
traces = self.ot_writer.pop_traces()
assert len(traces) == 1
spans = traces[0]
assert len(spans) == 1
span = spans[0]
assert span.error == 1
assert span.get_tag('error.msg') == 'f1 error'
assert 'Exception: f1 error' in span.get_tag('error.stack')
@mark_asyncio
def test_trace_multiple_calls(self):
# create multiple futures so that we expect multiple
# traces instead of a single one (helper not used)
@asyncio.coroutine
def coro():
# another traced coroutine
with self.ot_tracer.start_span('coroutine'):
yield from asyncio.sleep(0.01)
futures = [asyncio.ensure_future(coro()) for x in range(10)]
for future in futures:
yield from future
traces = self.ot_writer.pop_traces()
assert len(traces) == 10
assert len(traces[0]) == 1
assert traces[0][0].name == 'coroutine'
@pytest.mark.usefixtures('ot_tracer')
class TestTracerAsyncioCompatibility(AsyncioTestCase):
"""Ensure the opentracer works in tandem with the ddtracer and asyncio."""
@mark_asyncio
def test_trace_multiple_coroutines_ot_dd(self):
"""
Ensure we can trace from opentracer to ddtracer across asyncio
context switches.
"""
# if multiple coroutines have nested tracing, they must belong
# to the same trace
@asyncio.coroutine
def coro():
# another traced coroutine
with self.dd_tracer.trace('coroutine_2'):
return 42
with self.ot_tracer.start_active_span('coroutine_1'):
value = yield from coro()
# the coroutine has been called correctly
assert value == 42
# a single trace has been properly reported
traces = self.ot_tracer._dd_tracer.writer.pop_traces()
assert len(traces) == 1
assert len(traces[0]) == 2
assert traces[0][0].name == 'coroutine_1'
assert traces[0][1].name == 'coroutine_2'
# the parenting is correct
assert traces[0][0] == traces[0][1]._parent
assert traces[0][0].trace_id == traces[0][1].trace_id
@mark_asyncio
def test_trace_multiple_coroutines_dd_ot(self):
"""
Ensure we can trace from ddtracer to opentracer across asyncio
context switches.
"""
# if multiple coroutines have nested tracing, they must belong
# to the same trace
@asyncio.coroutine
def coro():
# another traced coroutine
with self.ot_tracer.start_span('coroutine_2'):
return 42
with self.dd_tracer.trace('coroutine_1'):
value = yield from coro()
# the coroutine has been called correctly
assert value == 42
# a single trace has been properly reported
traces = self.ot_tracer._dd_tracer.writer.pop_traces()
assert len(traces) == 1
assert len(traces[0]) == 2
assert traces[0][0].name == 'coroutine_1'
assert traces[0][1].name == 'coroutine_2'
# the parenting is correct
assert traces[0][0] == traces[0][1]._parent
assert traces[0][0].trace_id == traces[0][1].trace_id
@pytest.mark.skipif(
ddtrace.internal.context_manager.CONTEXTVARS_IS_AVAILABLE,
reason='only applicable to legacy asyncio provider'
)
class TestUtilsAsyncio(object):
"""Test the util routines of the opentracer with asyncio specific
configuration.
"""
def test_get_context_provider_for_scope_manager_asyncio(self):
scope_manager = AsyncioScopeManager()
ctx_prov = get_context_provider_for_scope_manager(scope_manager)
assert isinstance(
ctx_prov, ddtrace.contrib.asyncio.provider.AsyncioContextProvider
)
def test_tracer_context_provider_config(self):
tracer = ddtrace.opentracer.Tracer('mysvc', scope_manager=AsyncioScopeManager())
assert isinstance(
tracer._dd_tracer.context_provider,
ddtrace.contrib.asyncio.provider.AsyncioContextProvider,
)