|
| 1 | +import json |
1 | 2 | import os |
2 | 3 | import sys |
3 | 4 |
|
| 5 | +from hypothesis import given |
| 6 | +from hypothesis import strategies as st |
4 | 7 | import pytest |
5 | 8 |
|
6 | 9 | from ddtrace import Pin |
| 10 | +from ddtrace.contrib.pytest.plugin import _json_encode |
7 | 11 | from ddtrace.ext import test |
8 | 12 | from tests.utils import TracerTestCase |
9 | 13 |
|
@@ -107,18 +111,53 @@ def test_parameterize_case(self): |
107 | 111 | """ |
108 | 112 | import pytest |
109 | 113 |
|
110 | | - @pytest.mark.parametrize('abc', [1, 2, 3, 4, pytest.param(5, marks=pytest.mark.skip)]) |
| 114 | + class A: |
| 115 | + def __init__(self, name, value): |
| 116 | + self.name = name |
| 117 | + self.value = value |
| 118 | +
|
| 119 | + def item_param(): |
| 120 | + return 42 |
| 121 | +
|
| 122 | + @pytest.mark.parametrize( |
| 123 | + 'item', |
| 124 | + [ |
| 125 | + 1, |
| 126 | + 2, |
| 127 | + 3, |
| 128 | + 4, |
| 129 | + pytest.param(A("test_name", "value"), marks=pytest.mark.skip), |
| 130 | + pytest.param(A("test_name", A("inner_name", "value")), marks=pytest.mark.skip), |
| 131 | + pytest.param({"a": A("test_name", "value"), "b": [1, 2, 3]}, marks=pytest.mark.skip), |
| 132 | + pytest.param([1, 2, 3], marks=pytest.mark.skip), |
| 133 | + pytest.param(item_param, marks=pytest.mark.skip) |
| 134 | + ] |
| 135 | + ) |
111 | 136 | class Test1(object): |
112 | | - def test_1(self, abc): |
113 | | - assert abc in {1, 2, 3} |
| 137 | + def test_1(self, item): |
| 138 | + assert item in {1, 2, 3} |
114 | 139 | """ |
115 | 140 | ) |
116 | 141 | file_name = os.path.basename(py_file.strpath) |
117 | 142 | rec = self.inline_run("--ddtrace", file_name) |
118 | | - rec.assertoutcome(passed=3, failed=1, skipped=1) |
| 143 | + rec.assertoutcome(passed=3, failed=1, skipped=5) |
119 | 144 | spans = self.pop_spans() |
120 | 145 |
|
121 | | - assert len(spans) == 5 |
| 146 | + expected_params = [ |
| 147 | + 1, |
| 148 | + 2, |
| 149 | + 3, |
| 150 | + 4, |
| 151 | + {"name": "test_name", "value": "value"}, |
| 152 | + {"name": "test_name", "value": {"name": "inner_name", "value": "value"}}, |
| 153 | + {"a": {"name": "test_name", "value": "value"}, "b": [1, 2, 3]}, |
| 154 | + [1, 2, 3], |
| 155 | + ] |
| 156 | + assert len(spans) == 9 |
| 157 | + for i in range(len(spans) - 1): |
| 158 | + extracted_params = json.loads(spans[i].meta[test.PARAMETERS]) |
| 159 | + assert extracted_params == {"arguments": {"item": expected_params[i]}, "metadata": {}} |
| 160 | + assert "<function item_param at 0x" in json.loads(spans[8].meta[test.PARAMETERS])["arguments"]["item"] |
122 | 161 |
|
123 | 162 | def test_skip(self): |
124 | 163 | """Test parametrize case.""" |
@@ -290,3 +329,68 @@ def test_service(ddspan): |
290 | 329 | file_name = os.path.basename(py_file.strpath) |
291 | 330 | rec = self.subprocess_run("--ddtrace", file_name) |
292 | 331 | assert 0 == rec.ret |
| 332 | + |
| 333 | + |
| 334 | +class A(object): |
| 335 | + def __init__(self, name, value): |
| 336 | + self.name = name |
| 337 | + self.value = value |
| 338 | + |
| 339 | + |
| 340 | +simple_types = [st.none(), st.booleans(), st.text(), st.integers(), st.floats(allow_infinity=False, allow_nan=False)] |
| 341 | +complex_types = [st.functions(), st.dates(), st.decimals(), st.builds(A, name=st.text(), value=st.integers())] |
| 342 | + |
| 343 | + |
| 344 | +@given( |
| 345 | + st.dictionaries( |
| 346 | + st.text(), |
| 347 | + st.one_of( |
| 348 | + st.lists(st.one_of(*simple_types)), st.dictionaries(st.text(), st.one_of(*simple_types)), *simple_types |
| 349 | + ), |
| 350 | + ) |
| 351 | +) |
| 352 | +def test_custom_json_encoding_simple_types(obj): |
| 353 | + """Ensures the _json.encode helper encodes simple objects.""" |
| 354 | + encoded = _json_encode(obj) |
| 355 | + decoded = json.loads(encoded) |
| 356 | + assert obj == decoded |
| 357 | + |
| 358 | + |
| 359 | +@given( |
| 360 | + st.dictionaries( |
| 361 | + st.text(), |
| 362 | + st.one_of( |
| 363 | + st.lists(st.one_of(*complex_types)), st.dictionaries(st.text(), st.one_of(*complex_types)), *complex_types |
| 364 | + ), |
| 365 | + ) |
| 366 | +) |
| 367 | +def test_custom_json_encoding_python_objects(obj): |
| 368 | + """Ensures the _json_encode helper encodes complex objects into dicts of inner values or a string representation.""" |
| 369 | + encoded = _json_encode(obj) |
| 370 | + obj = json.loads( |
| 371 | + json.dumps(obj, default=lambda x: getattr(x, "__dict__", None) if getattr(x, "__dict__", None) else repr(x)) |
| 372 | + ) |
| 373 | + decoded = json.loads(encoded) |
| 374 | + assert obj == decoded |
| 375 | + |
| 376 | + |
| 377 | +def test_custom_json_encoding_side_effects(): |
| 378 | + """Ensures the _json_encode helper encodes objects with side effects (getattr, repr) without raising exceptions.""" |
| 379 | + dict_side_effect = Exception("side effect __dict__") |
| 380 | + repr_side_effect = Exception("side effect __repr__") |
| 381 | + |
| 382 | + class B(object): |
| 383 | + def __getattribute__(self, item): |
| 384 | + if item == "__dict__": |
| 385 | + raise dict_side_effect |
| 386 | + raise AttributeError() |
| 387 | + |
| 388 | + class C(object): |
| 389 | + def __repr__(self): |
| 390 | + raise repr_side_effect |
| 391 | + |
| 392 | + obj = {"b": B(), "c": C()} |
| 393 | + encoded = _json_encode(obj) |
| 394 | + decoded = json.loads(encoded) |
| 395 | + assert decoded["b"] == repr(dict_side_effect) |
| 396 | + assert decoded["c"] == repr(repr_side_effect) |
0 commit comments