Skip to content

Commit 056f0a3

Browse files
ericlazemmettbutlerromainkomorn-exdatadogmabdinur
authored
feat(unittest): add unittest support for test visibility (#6559)
## Motivation This PR aims to add unittest support for `dd-tracer-py` which is a built-in Python testing library widely used. So far, this PR is for test visibility only. It is planned to add full CI Visibility to unittest in the future. ## Changes Added test visibility support for unittests behind a feature flag. The `DD_CIVISIBILITY_UNITTEST_ENABLED` environment variable must be enabled in order to use unittest test visibility. ## Example You can find an example of the trace JSON in this [span](https://dd.datad0g.com/ci/test-runs?query=test_level%3Atest%20%40test.service%3Aericnavarro-test%20&currentTab=overview&eventStack=AgAAAYoI6g9uHpHMiwAAAAAAAAAYAAAAAEFZb0k2aEVjQUFEaEhyTWEyU1AwZkRoYgAAACQAAAAAMDE4YTA5MWUtNjZiNy00MTM1LWJmM2YtYTgxY2U5YWRhZDgz&graphType=flamegraph&index=citest&sort=time&spanID=9606617518799500644&testId=AgAAAYoI6g9uHpHMiwAAAAAAAAAYAAAAAEFZb0k2aEVjQUFEaEhyTWEyU1AwZkRoYgAAACQAAAAAMDE4YTA5MWUtNjZiNy00MTM1LWJmM2YtYTgxY2U5YWRhZDgz&trace=AgAAAYoI6g9uHpHMiwAAAAAAAAAYAAAAAEFZb0k2aEVjQUFEaEhyTWEyU1AwZkRoYgAAACQAAAAAMDE4YTA5MWUtNjZiNy00MTM1LWJmM2YtYTgxY2U5YWRhZDgz&start=1690988854106&end=1691593654106&paused=false) ## Checklist - [X] Change(s) are motivated and described in the PR description. - [X] Testing strategy is described if automated tests are not included in the PR. - [X] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [X] Change is maintainable (easy to change, telemetry, documentation). - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed. If no release note is required, add label `changelog/no-changelog`. - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment. - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Emmett Butler <[email protected]> Co-authored-by: Romain Komorn <[email protected]> Co-authored-by: Munir Abdinur <[email protected]>
1 parent 1a3c424 commit 056f0a3

File tree

22 files changed

+909
-4
lines changed

22 files changed

+909
-4
lines changed

.circleci/config.templ.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,13 @@ jobs:
784784
- run_test:
785785
pattern: 'pytest'
786786

787+
unittest:
788+
<<: *contrib_job
789+
steps:
790+
- run_test:
791+
pattern: 'unittest'
792+
793+
787794
asynctest:
788795
executor: ddtrace_dev
789796
steps:

.riot/requirements/10b31b2.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.8
3+
# by the following command:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/10b31b2.in
6+
#
7+
attrs==23.1.0
8+
coverage[toml]==7.3.0
9+
exceptiongroup==1.1.3
10+
hypothesis==6.45.0
11+
iniconfig==2.0.0
12+
mock==5.1.0
13+
msgpack==1.0.5
14+
opentracing==2.4.0
15+
packaging==23.1
16+
pluggy==1.3.0
17+
pytest==7.4.0
18+
pytest-cov==4.1.0
19+
pytest-mock==3.11.1
20+
sortedcontainers==2.4.0
21+
tomli==2.0.1

.riot/requirements/16b3d1d.txt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#
2+
# This file is autogenerated by pip-compile
3+
# To update, run:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/16b3d1d.in
6+
#
7+
attrs==22.1.0
8+
coverage==5.5
9+
hypothesis==5.33.2
10+
importlib-metadata==2.1.3
11+
iniconfig==1.1.1
12+
mock==3.0.5
13+
msgpack==1.0.5
14+
opentracing==2.4.0
15+
packaging==20.9
16+
pathlib2==2.3.7.post1
17+
pluggy==0.13.1
18+
py==1.11.0
19+
pyparsing==2.4.7
20+
pytest-cov==2.12.1
21+
pytest-mock==3.5.1
22+
pytest==6.1.2
23+
six==1.16.0
24+
sortedcontainers==2.4.0
25+
toml==0.10.2
26+
zipp==1.2.0

.riot/requirements/1b0a62d.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.11
3+
# by the following command:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/1b0a62d.in
6+
#
7+
attrs==23.1.0
8+
coverage[toml]==7.3.0
9+
hypothesis==6.45.0
10+
iniconfig==2.0.0
11+
mock==5.1.0
12+
msgpack==1.0.5
13+
opentracing==2.4.0
14+
packaging==23.1
15+
pluggy==1.3.0
16+
pytest==7.4.0
17+
pytest-cov==4.1.0
18+
pytest-mock==3.11.1
19+
sortedcontainers==2.4.0

.riot/requirements/1bd6f1c.txt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#
2+
# This file is autogenerated by pip-compile
3+
# To update, run:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/1bd6f1c.in
6+
#
7+
atomicwrites==1.4.1
8+
attrs==21.4.0
9+
backports.functools-lru-cache==1.6.6
10+
configparser==4.0.2
11+
contextlib2==0.6.0.post1
12+
coverage==5.5
13+
enum34==1.1.10
14+
funcsigs==1.0.2
15+
futures==3.4.0
16+
hypothesis==4.57.1
17+
importlib-metadata==2.1.3
18+
mock==3.0.5
19+
more-itertools==5.0.0
20+
msgpack==1.0.5
21+
opentracing==2.4.0
22+
packaging==20.9
23+
pathlib2==2.3.7.post1
24+
pluggy==0.13.1
25+
py==1.11.0
26+
pyparsing==2.4.7
27+
pytest-cov==2.12.1
28+
pytest-mock==2.0.0
29+
pytest==4.6.11
30+
scandir==1.10.0
31+
six==1.16.0
32+
sortedcontainers==2.4.0
33+
toml==0.10.2
34+
typing==3.10.0.0
35+
wcwidth==0.2.6
36+
zipp==1.2.0

.riot/requirements/1cf1760.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.7
3+
# by the following command:
4+
#
5+
# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1cf1760.in
6+
#
7+
attrs==23.1.0
8+
coverage[toml]==7.2.7
9+
exceptiongroup==1.1.3
10+
hypothesis==6.45.0
11+
importlib-metadata==6.7.0
12+
iniconfig==2.0.0
13+
mock==5.1.0
14+
msgpack==1.0.5
15+
opentracing==2.4.0
16+
packaging==23.1
17+
pluggy==1.2.0
18+
pytest==7.4.0
19+
pytest-cov==4.1.0
20+
pytest-mock==3.11.1
21+
sortedcontainers==2.4.0
22+
tomli==2.0.1
23+
typing-extensions==4.7.1
24+
zipp==3.15.0

.riot/requirements/3013656.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.10
3+
# by the following command:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/3013656.in
6+
#
7+
attrs==23.1.0
8+
coverage[toml]==7.3.0
9+
exceptiongroup==1.1.3
10+
hypothesis==6.45.0
11+
iniconfig==2.0.0
12+
mock==5.1.0
13+
msgpack==1.0.5
14+
opentracing==2.4.0
15+
packaging==23.1
16+
pluggy==1.3.0
17+
pytest==7.4.0
18+
pytest-cov==4.1.0
19+
pytest-mock==3.11.1
20+
sortedcontainers==2.4.0
21+
tomli==2.0.1

.riot/requirements/d6a02a4.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#
2+
# This file is autogenerated by pip-compile with python 3.6
3+
# To update, run:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/d6a02a4.in
6+
#
7+
attrs==22.2.0
8+
coverage[toml]==6.2
9+
hypothesis==6.31.6
10+
importlib-metadata==4.8.3
11+
iniconfig==1.1.1
12+
mock==5.1.0
13+
msgpack==1.0.5
14+
opentracing==2.4.0
15+
packaging==21.3
16+
pluggy==1.0.0
17+
py==1.11.0
18+
pyparsing==3.1.1
19+
pytest==7.0.1
20+
pytest-cov==4.0.0
21+
pytest-mock==3.6.1
22+
sortedcontainers==2.4.0
23+
tomli==1.2.3
24+
typing-extensions==4.1.1
25+
zipp==3.6.0

.riot/requirements/d75c291.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.9
3+
# by the following command:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/d75c291.in
6+
#
7+
attrs==23.1.0
8+
coverage[toml]==7.3.0
9+
exceptiongroup==1.1.3
10+
hypothesis==6.45.0
11+
iniconfig==2.0.0
12+
mock==5.1.0
13+
msgpack==1.0.5
14+
opentracing==2.4.0
15+
packaging==23.1
16+
pluggy==1.3.0
17+
pytest==7.4.0
18+
pytest-cov==4.1.0
19+
pytest-mock==3.11.1
20+
sortedcontainers==2.4.0
21+
tomli==2.0.1

ddtrace/_monkey.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"openai": True,
9090
"langchain": True,
9191
"subprocess": True,
92+
"unittest": True,
9293
}
9394

9495

ddtrace/contrib/pytest/plugin.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from ddtrace.contrib.pytest.constants import HELP_MSG
2727
from ddtrace.contrib.pytest.constants import KIND
2828
from ddtrace.contrib.pytest.constants import XFAIL_REASON
29+
from ddtrace.contrib.unittest import unpatch as unpatch_unittest
2930
from ddtrace.ext import SpanTypes
3031
from ddtrace.ext import test
3132
from ddtrace.internal.ci_visibility import CIVisibility as _CIVisibility
@@ -214,7 +215,8 @@ def _start_test_module_span(pytest_package_item=None, pytest_module_item=None):
214215
test_module_span.set_tag_str(test.FRAMEWORK_VERSION, pytest.__version__)
215216
test_module_span.set_tag_str(test.COMMAND, _get_pytest_command(item.config))
216217
test_module_span.set_tag_str(_EVENT_TYPE, _MODULE_TYPE)
217-
test_module_span.set_tag_str(_SESSION_ID, str(test_session_span.span_id))
218+
if test_session_span:
219+
test_module_span.set_tag_str(_SESSION_ID, str(test_session_span.span_id))
218220
test_module_span.set_tag_str(_MODULE_ID, str(test_module_span.span_id))
219221
test_module_span.set_tag_str(test.MODULE, _get_module_name(item, is_package))
220222
test_module_span.set_tag_str(test.MODULE_PATH, _get_module_path(item))
@@ -250,7 +252,8 @@ def _start_test_suite_span(item, test_module_span, should_enable_coverage=False)
250252
test_suite_span.set_tag_str(test.FRAMEWORK_VERSION, pytest.__version__)
251253
test_suite_span.set_tag_str(test.COMMAND, _get_pytest_command(item.config))
252254
test_suite_span.set_tag_str(_EVENT_TYPE, _SUITE_TYPE)
253-
test_suite_span.set_tag_str(_SESSION_ID, str(test_session_span.span_id))
255+
if test_session_span:
256+
test_suite_span.set_tag_str(_SESSION_ID, str(test_session_span.span_id))
254257
test_suite_span.set_tag_str(_SUITE_ID, str(test_suite_span.span_id))
255258
test_module_path = None
256259
if test_module_span is not None:
@@ -300,6 +303,7 @@ def pytest_addoption(parser):
300303

301304

302305
def pytest_configure(config):
306+
unpatch_unittest()
303307
config.addinivalue_line("markers", "dd_tags(**kwargs): add tags to current span")
304308
if is_enabled(config):
305309
_CIVisibility.enable(config=ddtrace.config.pytest)
@@ -481,7 +485,8 @@ def pytest_runtest_protocol(item, nextitem):
481485
span.set_tag_str(_EVENT_TYPE, SpanTypes.TEST)
482486
span.set_tag_str(test.NAME, item.name)
483487
span.set_tag_str(test.COMMAND, _get_pytest_command(item.config))
484-
span.set_tag_str(_SESSION_ID, str(test_session_span.span_id))
488+
if test_session_span:
489+
span.set_tag_str(_SESSION_ID, str(test_session_span.span_id))
485490

486491
span.set_tag_str(_MODULE_ID, str(test_module_span.span_id))
487492
span.set_tag_str(test.MODULE, test_module_span.get_tag(test.MODULE))

ddtrace/contrib/unittest/__init__.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
The unittest integration traces test executions.
3+
4+
5+
Enabling
6+
~~~~~~~~
7+
8+
The unittest integration is enabled automatically when using
9+
:ref:`ddtrace-run<ddtracerun>` or :func:`patch_all()<ddtrace.patch_all>`.
10+
11+
Alternately, use :func:`patch()<ddtrace.patch>` to manually enable the integration::
12+
13+
from ddtrace import patch
14+
patch(unittest=True)
15+
16+
Global Configuration
17+
~~~~~~~~~~~~~~~~~~~~
18+
19+
.. py:data:: ddtrace.config.unittest["operation_name"]
20+
21+
The operation name reported by default for unittest traces.
22+
23+
This option can also be set with the ``DD_UNITTEST_OPERATION_NAME`` environment
24+
variable.
25+
26+
Default: ``"unittest.test"``
27+
"""
28+
from .patch import patch
29+
from .patch import unpatch
30+
31+
32+
__all__ = ["patch", "unpatch"]
33+
34+
patch()

ddtrace/contrib/unittest/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
COMPONENT_VALUE = "unittest"
2+
FRAMEWORK = "unittest"
3+
KIND = "test"
4+
5+
TEST_OPERATION_NAME = "unittest.test"

0 commit comments

Comments
 (0)