Skip to content

Commit 7c86987

Browse files
Yun-Kimmergify-bot
authored andcommitted
fix[pytest]: Default parameter JSON encoding to use repr (#2684)
* Remove JSON encoding for pytest parameters, default to string representation of parameters * Added type hinting (cherry picked from commit f5bc986)
1 parent 465edcd commit 7c86987

File tree

3 files changed

+48
-7
lines changed

3 files changed

+48
-7
lines changed

ddtrace/contrib/pytest/plugin.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
from typing import Dict
23

34
import pytest
45

@@ -107,8 +108,14 @@ def pytest_runtest_protocol(item, nextitem):
107108
# Parameterized test cases will have a `callspec` attribute attached to the pytest Item object.
108109
# Pytest docs: https://docs.pytest.org/en/6.2.x/reference.html#pytest.Function
109110
if getattr(item, "callspec", None):
110-
params = {"arguments": item.callspec.params, "metadata": {}}
111-
span.set_tag(test.PARAMETERS, json.dumps(params, default=repr))
111+
parameters = {"arguments": {}, "metadata": {}} # type: Dict[str, Dict[str, str]]
112+
for param_name, param_val in item.callspec.params.items():
113+
try:
114+
parameters["arguments"][param_name] = repr(param_val)
115+
except Exception:
116+
parameters["arguments"][param_name] = "Could not encode"
117+
log.warning("Failed to encode %r", param_name, exc_info=True)
118+
span.set_tag(test.PARAMETERS, json.dumps(parameters))
112119

113120
markers = [marker.kwargs for marker in item.iter_markers(name="dd_tags")]
114121
for tags in markers:
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
fixes:
3+
- |
4+
Fixed JSON encoding errors in the pytest plugin for parameterized tests with dictionary parameters with tuple keys.
5+
The pytest plugin now always JSON encodes the string representations of test parameters.

tests/contrib/pytest/test_pytest.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,10 @@ def test_1(self, item):
123123
expected_params = [1, 2, 3, 4, [1, 2, 3]]
124124
assert len(spans) == 5
125125
for i in range(len(expected_params)):
126-
extracted_params = json.loads(spans[i].meta[test.PARAMETERS])
127-
assert extracted_params == {"arguments": {"item": expected_params[i]}, "metadata": {}}
126+
assert json.loads(spans[i].meta[test.PARAMETERS]) == {
127+
"arguments": {"item": str(expected_params[i])},
128+
"metadata": {},
129+
}
128130

129131
def test_parameterize_case_complex_objects(self):
130132
"""Test parametrize case with complex objects."""
@@ -153,6 +155,7 @@ def item_param():
153155
pytest.param({"a": A("test_name", "value"), "b": [1, 2, 3]}, marks=pytest.mark.skip),
154156
pytest.param(MagicMock(value=MagicMock()), marks=pytest.mark.skip),
155157
pytest.param(circular_reference, marks=pytest.mark.skip),
158+
pytest.param({("x", "y"): 12345}, marks=pytest.mark.skip),
156159
]
157160
)
158161
class Test1(object):
@@ -162,7 +165,7 @@ def test_1(self, item):
162165
)
163166
file_name = os.path.basename(py_file.strpath)
164167
rec = self.inline_run("--ddtrace", file_name)
165-
rec.assertoutcome(skipped=6)
168+
rec.assertoutcome(skipped=7)
166169
spans = self.pop_spans()
167170

168171
# Since object will have arbitrary addresses, only need to ensure that
@@ -171,14 +174,40 @@ def test_1(self, item):
171174
"test_parameterize_case_complex_objects.A",
172175
"test_parameterize_case_complex_objects.A",
173176
"<function item_param at 0x",
174-
'"a": "<test_parameterize_case_complex_objects.A',
177+
"'a': <test_parameterize_case_complex_objects.A",
175178
"<MagicMock id=",
176179
"test_parameterize_case_complex_objects.A",
180+
"{('x', 'y'): 12345}",
177181
]
178-
assert len(spans) == 6
182+
assert len(spans) == 7
179183
for i in range(len(expected_params_contains)):
180184
assert expected_params_contains[i] in spans[i].meta[test.PARAMETERS]
181185

186+
def test_parameterize_case_encoding_error(self):
187+
"""Test parametrize case with complex objects that cannot be JSON encoded."""
188+
py_file = self.testdir.makepyfile(
189+
"""
190+
from mock import MagicMock
191+
import pytest
192+
193+
class A:
194+
def __repr__(self):
195+
raise Exception("Cannot __repr__")
196+
197+
@pytest.mark.parametrize('item',[A()])
198+
class Test1(object):
199+
def test_1(self, item):
200+
assert True
201+
"""
202+
)
203+
file_name = os.path.basename(py_file.strpath)
204+
rec = self.inline_run("--ddtrace", file_name)
205+
rec.assertoutcome(passed=1)
206+
spans = self.pop_spans()
207+
208+
assert len(spans) == 1
209+
assert json.loads(spans[0].meta[test.PARAMETERS]) == {"arguments": {"item": "Could not encode"}, "metadata": {}}
210+
182211
def test_skip(self):
183212
"""Test parametrize case."""
184213
py_file = self.testdir.makepyfile(

0 commit comments

Comments
 (0)