|
3 | 3 | import re |
4 | 4 | import sys |
5 | 5 | import time |
| 6 | +import traceback |
6 | 7 | from unittest.case import SkipTest |
7 | 8 |
|
8 | 9 | import mock |
|
19 | 20 | from ddtrace.constants import VERSION_KEY |
20 | 21 | from ddtrace.ext import SpanTypes |
21 | 22 | from ddtrace.internal import core |
| 23 | +from ddtrace.internal.compat import PYTHON_VERSION_INFO |
22 | 24 | from ddtrace.trace import Span |
23 | 25 | from tests.subprocesstest import run_in_subprocess |
24 | 26 | from tests.utils import TracerTestCase |
@@ -286,11 +288,21 @@ def wrapper(): |
286 | 288 | assert stack, "No error stack collected" |
287 | 289 | # one header "Traceback (most recent call last):" and one footer "ZeroDivisionError: division by zero" |
288 | 290 | header_and_footer_lines = 2 |
289 | | - # Python 3.13 adds extra lines to the traceback: |
290 | | - # File dd-trace-py/tests/tracer/test_span.py", line 279, in test_custom_traceback_size_with_error |
291 | | - # wrapper() |
292 | | - # ~~~~~~~^^ |
293 | | - multiplier = 3 if "~~" in stack else 2 |
| 291 | + multiplier = 2 |
| 292 | + if PYTHON_VERSION_INFO >= (3, 13): |
| 293 | + # Python 3.13 adds extra lines to the traceback: |
| 294 | + # File dd-trace-py/tests/tracer/test_span.py", line 279, in test_custom_traceback_size_with_error |
| 295 | + # wrapper() |
| 296 | + # ~~~~~~~^^ |
| 297 | + multiplier = 3 |
| 298 | + elif PYTHON_VERSION_INFO >= (3, 11): |
| 299 | + # Python 3.11 adds one extra line to the traceback: |
| 300 | + # File dd-trace-py/tests/tracer/test_span.py", line 272, in divide_by_zero |
| 301 | + # 1 / 0 |
| 302 | + # ~~^~~ |
| 303 | + # ZeroDivisionError: division by zero |
| 304 | + header_and_footer_lines += 1 |
| 305 | + |
294 | 306 | assert ( |
295 | 307 | len(stack.splitlines()) == tb_length_limit * multiplier + header_and_footer_lines |
296 | 308 | ), "stacktrace should contain two lines per entry" |
@@ -954,3 +966,81 @@ def _(span, *exc_info): |
954 | 966 | raise AssertionError("should have raised") |
955 | 967 | finally: |
956 | 968 | core.reset_listeners("span.exception") |
| 969 | + |
| 970 | + |
| 971 | +def test_get_traceback_exceeds_max_value_length(): |
| 972 | + """Test with a long traceback that should be truncated.""" |
| 973 | + |
| 974 | + def deep_error(n): |
| 975 | + if n > 0: |
| 976 | + deep_error(n - 1) |
| 977 | + else: |
| 978 | + raise RuntimeError("Deep recursion error") |
| 979 | + |
| 980 | + exc_type, exc_val, exc_tb = None, None, None |
| 981 | + try: |
| 982 | + deep_error(100) # Create a large traceback |
| 983 | + except Exception as e: |
| 984 | + exc_type, exc_val, exc_tb = e.__class__, e, e.__traceback__ |
| 985 | + |
| 986 | + span = Span("test.span") |
| 987 | + with mock.patch("ddtrace._trace.span.MAX_SPAN_META_VALUE_LEN", 260): |
| 988 | + result = span._get_traceback(exc_type, exc_val, exc_tb, limit=100) |
| 989 | + assert "Deep recursion error" in result |
| 990 | + assert len(result) <= 260 # Should be truncated |
| 991 | + |
| 992 | + |
| 993 | +def test_get_traceback_exact_limit(): |
| 994 | + """Test a case where the traceback length is exactly at the limit.""" |
| 995 | + |
| 996 | + def deep_error(n): |
| 997 | + if n > 0: |
| 998 | + deep_error(n - 1) |
| 999 | + else: |
| 1000 | + raise RuntimeError("Deep recursion error") |
| 1001 | + |
| 1002 | + exc_type, exc_val, exc_tb = None, None, None |
| 1003 | + try: |
| 1004 | + deep_error(100) # Create a large traceback |
| 1005 | + except Exception as e: |
| 1006 | + exc_type, exc_val, exc_tb = e.__class__, e, e.__traceback__ |
| 1007 | + |
| 1008 | + span = Span("test.span") |
| 1009 | + formatted_exception = traceback.format_exception(exc_type, exc_val, exc_tb) |
| 1010 | + formatted_exception = [s + "\n" for item in formatted_exception for s in item.split("\n") if s] |
| 1011 | + exc_len = len(formatted_exception) |
| 1012 | + result = span._get_traceback(exc_type, exc_val, exc_tb, limit=exc_len) |
| 1013 | + split_result = result.splitlines() |
| 1014 | + split_result = [s + "\n" for item in split_result for s in item.split("\n") if s] |
| 1015 | + |
| 1016 | + if PYTHON_VERSION_INFO >= (3, 11): |
| 1017 | + exc_len -= 1 # From Python 3.11, adds an extra line to the traceback |
| 1018 | + |
| 1019 | + assert len(split_result) == exc_len - 2 # Should be exactly the same length as the traceback |
| 1020 | + |
| 1021 | + |
| 1022 | +def test_get_traceback_honors_config_traceback_max_size(): |
| 1023 | + class CustomConfig: |
| 1024 | + _span_traceback_max_size = 2 # Force a zero limit |
| 1025 | + |
| 1026 | + def deep_error(n): |
| 1027 | + if n > 0: |
| 1028 | + deep_error(n - 1) |
| 1029 | + else: |
| 1030 | + raise RuntimeError("Deep recursion error") |
| 1031 | + |
| 1032 | + exc_type, exc_val, exc_tb = None, None, None |
| 1033 | + try: |
| 1034 | + deep_error(100) # Create a large traceback |
| 1035 | + except Exception as e: |
| 1036 | + exc_type, exc_val, exc_tb = e.__class__, e, e.__traceback__ |
| 1037 | + |
| 1038 | + span = Span("test.span") |
| 1039 | + with mock.patch("ddtrace._trace.span.config", CustomConfig): |
| 1040 | + result = span._get_traceback(exc_type, exc_val, exc_tb) |
| 1041 | + |
| 1042 | + assert isinstance(result, str) |
| 1043 | + split_result = result.splitlines() |
| 1044 | + split_result = [s + "\n" for item in split_result for s in item.split("\n") if s] |
| 1045 | + assert len(split_result) < 8 # Value is 5 for Python 3.10 |
| 1046 | + assert len(result) < 410 # Value is 377 for Python 3.10 |
0 commit comments