Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
66 changes: 66 additions & 0 deletions tests/test_spans.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,3 +763,69 @@ def test_successful_tool_result_is_not_error():
assert tool_spans[0].status.status_code != StatusCode.ERROR, (
"A successful tool result must not produce an ERROR span"
)


def test_middle_truncate_handles_zero_budget():
"""With max_chars=0, should return just the elision marker, not the full text."""
input_text = "X" * 100
result = spans.middle_truncate(input_text, 0)
# Should not return the full text
assert len(result) < len(input_text), "Zero budget should truncate, not return full text"
# Should include elision marker
assert "chars elided" in result
# Should not include the 100 X's
assert "X" * 50 not in result


def test_middle_truncate_handles_negative_budget():
"""With max_chars<0, should return just the elision marker, not the full text."""
input_text = "Y" * 100
result = spans.middle_truncate(input_text, -5)
# Should not return the full text
assert len(result) < len(input_text), "Negative budget should truncate, not return full text"
# Should include elision marker
assert "chars elided" in result
# Should not include duplicate/overlapping content
assert result.count("Y") <= 1, "Should not contain overlapping Y characters"


def test_middle_truncate_with_small_positive_budget():
"""With max_chars=10, should still truncate but preserve head and tail."""
input_text = "A" * 100 + "B" * 100
result = spans.middle_truncate(input_text, 10)
assert len(result) < len(input_text)
assert "middle elided" in result
assert result.startswith("A")
assert result.endswith("B")


def test_load_config_clamps_zero_max_chars():
"""When TRACEROOT_PLUGIN_MAX_CHARS=0, should fall back to default 20000."""
import os
old_val = os.environ.get("TRACEROOT_PLUGIN_MAX_CHARS")
try:
os.environ["TRACEROOT_PLUGIN_MAX_CHARS"] = "0"
from traceroot_observability.config import load_config
config = load_config()
assert config.max_chars == 20000, "Zero max_chars should fallback to default"
finally:
if old_val is not None:
os.environ["TRACEROOT_PLUGIN_MAX_CHARS"] = old_val
elif "TRACEROOT_PLUGIN_MAX_CHARS" in os.environ:
del os.environ["TRACEROOT_PLUGIN_MAX_CHARS"]


def test_load_config_clamps_negative_max_chars():
"""When TRACEROOT_PLUGIN_MAX_CHARS=-5, should fall back to default 20000."""
import os
old_val = os.environ.get("TRACEROOT_PLUGIN_MAX_CHARS")
try:
os.environ["TRACEROOT_PLUGIN_MAX_CHARS"] = "-5"
from traceroot_observability.config import load_config
config = load_config()
assert config.max_chars == 20000, "Negative max_chars should fallback to default"
finally:
if old_val is not None:
os.environ["TRACEROOT_PLUGIN_MAX_CHARS"] = old_val
elif "TRACEROOT_PLUGIN_MAX_CHARS" in os.environ:
del os.environ["TRACEROOT_PLUGIN_MAX_CHARS"]
4 changes: 4 additions & 0 deletions traceroot_observability/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class Config:
def load_config() -> Config:
try:
max_chars = int(_env_opt("TRACEROOT_PLUGIN_MAX_CHARS") or "20000")
# A 0/negative budget disables truncation entirely (see middle_truncate),
# so treat an out-of-range value the same as a non-integer: fall back.
if max_chars < 1:
max_chars = 20000
except ValueError:
max_chars = 20000
return Config(
Expand Down
4 changes: 4 additions & 0 deletions traceroot_observability/spans.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ def middle_truncate(s: str, max_chars: int) -> str:
if len(s) <= max_chars:
return s
half = max_chars // 2
if half <= 0:
# Budget too small to keep any head/tail; `s[-0:]` would return the
# whole string, so emit only the elision marker.
return f"…[{len(s)} chars elided]…"
return f"{s[:half]}\n…[{len(s)} chars, middle elided]…\n{s[-half:]}"


Expand Down