Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: pytest integration #1741

Merged
merged 44 commits into from
Nov 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ee34bef
feat: pytest integration
jirikuncar Oct 19, 2020
6ade010
add skip reason
jirikuncar Oct 20, 2020
279148c
add release notes
jirikuncar Oct 20, 2020
093088e
move to plugin and add analytics sample rate
jirikuncar Oct 20, 2020
7a8384e
update ci matrix
jirikuncar Oct 20, 2020
e0e127e
use pytest_runtest_protocol
jirikuncar Oct 20, 2020
b73b909
add pytest to circleci
jirikuncar Oct 20, 2020
617059c
use config for analytics
jirikuncar Oct 20, 2020
9a7b1fe
add support for ci and git tags
jirikuncar Oct 21, 2020
20cc0ea
more tests and fixed code style
jirikuncar Oct 21, 2020
2fa8255
simlify
jirikuncar Oct 22, 2020
20c4b4d
module import
jirikuncar Oct 22, 2020
9d74909
add simple docstring
jirikuncar Oct 22, 2020
f945d83
span.kind
jirikuncar Oct 22, 2020
9716a74
add deprecated git.commit_sha
jirikuncar Oct 22, 2020
7ee6a1c
deprecated field fix
jirikuncar Oct 22, 2020
ff142f3
black
jirikuncar Oct 22, 2020
b2daffc
ddtrace.ext.ci:tags
jirikuncar Oct 26, 2020
e2fa58a
Merge remote-tracking branch 'origin/master' into jirikuncar/pytest
jirikuncar Oct 26, 2020
45ab2ec
add tests for azurepipelines
jirikuncar Oct 27, 2020
266dc99
black
jirikuncar Oct 27, 2020
c3eaae7
appveyor
jirikuncar Oct 27, 2020
7667b46
use json fixtures
jirikuncar Oct 28, 2020
7f03003
test service_name
jirikuncar Oct 28, 2020
230b7b6
Merge remote-tracking branch 'origin/master' into jirikuncar/pytest
jirikuncar Oct 28, 2020
faaa237
flake8
jirikuncar Oct 28, 2020
4be2190
bitbucket
jirikuncar Oct 28, 2020
0ec6bfd
travis
jirikuncar Oct 28, 2020
bf86ce1
test all ci providers
jirikuncar Nov 2, 2020
a609181
Merge remote-tracking branch 'origin/master' into jirikuncar/pytest
jirikuncar Nov 2, 2020
4c3fc12
Merge remote-tracking branch 'origin/master' into jirikuncar/pytest
jirikuncar Nov 3, 2020
2e9e40e
configurable operation name
jirikuncar Nov 3, 2020
844a868
black
jirikuncar Nov 3, 2020
4418e91
document configuration options
jirikuncar Nov 3, 2020
1e0982e
Merge branch 'master' into jirikuncar/pytest
jirikuncar Nov 4, 2020
baf8aa0
add circleci entry
Kyle-Verhoog Nov 4, 2020
041f4b1
Merge branch 'master' into jirikuncar/pytest
Kyle-Verhoog Nov 6, 2020
fcdff8a
Merge branch 'master' into jirikuncar/pytest
Kyle-Verhoog Nov 6, 2020
24b9e87
Merge branch 'master' into jirikuncar/pytest
jirikuncar Nov 9, 2020
e9da9cd
Merge branch 'master' into jirikuncar/pytest
Kyle-Verhoog Nov 11, 2020
56a05d8
use pytest 3 for flask and aiohttp
Kyle-Verhoog Nov 13, 2020
efa6257
Merge remote-tracking branch 'origin/master' into jirikuncar/pytest
Kyle-Verhoog Nov 13, 2020
ef96a5b
add pytest to docs
Kyle-Verhoog Nov 13, 2020
f4c0b0d
fix typo
Kyle-Verhoog Nov 13, 2020
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
8 changes: 8 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,12 @@ jobs:
- run_tox_scenario:
pattern: '^pylibmc_contrib-'

pytest:
executor: ddtrace_dev
steps:
- run_tox_scenario:
pattern: '^pytest_contrib'

pymemcache:
<<: *contrib_job
docker:
Expand Down Expand Up @@ -915,6 +921,7 @@ requires_tests: &requires_tests
- pynamodb
- pyodbc
- pyramid
- pytest
- redis
- rediscluster
- requests
Expand Down Expand Up @@ -1016,6 +1023,7 @@ workflows:
- pynamodb: *requires_pre_test
- pyodbc: *requires_pre_test
- pyramid: *requires_pre_test
- pytest: *requires_pre_test
Kyle-Verhoog marked this conversation as resolved.
Show resolved Hide resolved
- redis: *requires_pre_test
- rediscluster: *requires_pre_test
- requests: *requires_pre_test
Expand Down
3 changes: 3 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

import pytest

# DEV: Enable "testdir" fixture https://docs.pytest.org/en/stable/reference.html#testdir
pytest_plugins = ("pytester",)

PY_DIR_PATTERN = re.compile(r"^py[23][0-9]$")


Expand Down
1 change: 1 addition & 0 deletions ddtrace/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
VERSION_KEY = 'version'
SERVICE_KEY = 'service.name'
SERVICE_VERSION_KEY = 'service.version'
SPAN_KIND = 'span.kind'
SPAN_MEASURED_KEY = '_dd.measured'

NUMERIC_TAGS = (ANALYTICS_SAMPLE_RATE_KEY, )
Expand Down
49 changes: 49 additions & 0 deletions ddtrace/contrib/pytest/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
The pytest integration traces test executions.

Enabling
~~~~~~~~

Enable traced execution of tests using ``pytest`` runner by
running ``pytest --ddtrace`` or by modifying any configuration
file read by pytest (``pytest.ini``, ``setup.cfg``, ...)::

[pytest]
ddtrace = 1

jirikuncar marked this conversation as resolved.
Show resolved Hide resolved

Global Configuration
~~~~~~~~~~~~~~~~~~~~

.. py:data:: ddtrace.config.pytest["service"]

The service name reported by default for pytest traces.

This option can also be set with the ``DD_PYTEST_SERVICE`` environment
variable.

Default: ``"pytest"``


.. py:data:: ddtrace.config.pytest["operation_name"]

The operation name reported by default for pytest traces.

This option can also be set with the ``DD_PYTEST_OPERATION_NAME`` environment
variable.

Default: ``"pytest.test"``
"""

from ddtrace import config
jirikuncar marked this conversation as resolved.
Show resolved Hide resolved

from ...utils.formats import get_env

# pytest default settings
config._add(
"pytest",
dict(
_default_service="pytest",
operation_name=get_env("pytest", "operation_name", default="pytest.test"),
),
)
4 changes: 4 additions & 0 deletions ddtrace/contrib/pytest/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FRAMEWORK = "pytest"
KIND = "test"

HELP_MSG = "Enable tracing of pytest functions."
124 changes: 124 additions & 0 deletions ddtrace/contrib/pytest/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import pytest

from ddtrace import config as ddconfig

from ..trace_utils import int_service
from ...constants import SPAN_KIND
from ...ext import SpanTypes, ci, test
from ...pin import Pin
from .constants import FRAMEWORK, HELP_MSG, KIND


def is_enabled(config):
"""Check if the ddtrace plugin is enabled."""
return config.getoption("ddtrace") or config.getini("ddtrace")


def _extract_span(item):
"""Extract span from `pytest.Item` instance."""
return getattr(item, "_datadog_span", None)


def _store_span(item, span):
"""Store span at `pytest.Item` instance."""
setattr(item, "_datadog_span", span)


def pytest_addoption(parser):
"""Add ddtrace options."""
group = parser.getgroup("ddtrace")

group._addoption(
"--ddtrace",
action="store_true",
dest="ddtrace",
default=False,
help=HELP_MSG,
)

parser.addini("ddtrace", HELP_MSG, type="bool")


def pytest_configure(config):
config.addinivalue_line("markers", "dd_tags(**kwargs): add tags to current span")

if is_enabled(config):
Pin(tags=ci.tags(), _config=ddconfig.pytest).onto(config)


def pytest_sessionfinish(session, exitstatus):
"""Flush open tracer."""
pin = Pin.get_from(session.config)
if pin is not None:
pin.tracer.shutdown()


@pytest.fixture(scope="function")
def ddspan(request):
pin = Pin.get_from(request.config)
if pin:
return _extract_span(request.node)


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_protocol(item, nextitem):
pin = Pin.get_from(item.config)
if pin is None:
yield
return

with pin.tracer.trace(
Kyle-Verhoog marked this conversation as resolved.
Show resolved Hide resolved
ddconfig.pytest.operation_name,
jirikuncar marked this conversation as resolved.
Show resolved Hide resolved
service=int_service(pin, ddconfig.pytest),
jirikuncar marked this conversation as resolved.
Show resolved Hide resolved
resource=item.nodeid,
span_type=SpanTypes.TEST.value,
) as span:
span.set_tags(pin.tags)
span.set_tag(SPAN_KIND, KIND)
span.set_tag(test.FRAMEWORK, FRAMEWORK)
span.set_tag(test.NAME, item.name)
span.set_tag(test.SUITE, item.module.__name__)
span.set_tag(test.TYPE, SpanTypes.TEST.value)

markers = [marker.kwargs for marker in item.iter_markers(name="dd_tags")]
for tags in markers:
span.set_tags(tags)

_store_span(item, span)

yield


def _extract_reason(call):
if call.excinfo is not None:
return call.excinfo.value


@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""Store outcome for tracing."""
outcome = yield

span = _extract_span(item)
if span is None:
return

called_without_status = call.when == "call" and span.get_tag(test.STATUS) is None
failed_setup = call.when == "setup" and call.excinfo is not None
if not called_without_status and not failed_setup:
return

try:
result = outcome.get_result()
if result.skipped:
span.set_tag(test.STATUS, test.Status.SKIP.value)
reason = _extract_reason(call)
if reason is not None:
span.set_tag(test.SKIP_REASON, reason)
elif result.passed:
span.set_tag(test.STATUS, test.Status.PASS.value)
else:
raise RuntimeWarning(result)
except Exception:
span.set_traceback()
span.set_tag(test.STATUS, test.Status.FAIL.value)
1 change: 1 addition & 0 deletions ddtrace/ext/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class SpanTypes(Enum):
REDIS = "redis"
SQL = "sql"
TEMPLATE = "template"
TEST = "test"
WEB = "web"
WORKER = "worker"

Expand Down
Loading