Skip to content
Merged
Changes from 1 commit
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
51 changes: 35 additions & 16 deletions backend/tests/test_tool_output_budget_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from __future__ import annotations

import contextlib
import json
import os
import tempfile
Expand Down Expand Up @@ -66,6 +67,28 @@ def _make_request(tool_name: str = "remote_executor", tool_call_id: str = "tc-1"
)


@contextlib.contextmanager
def _unwritable_outputs_path():
"""Yield an ``outputs_path`` that ``os.makedirs`` cannot create, on any platform.

The parent component is a regular file, so creating a directory below it
fails with an ``OSError`` subclass everywhere (``NotADirectoryError`` on
POSIX, ``FileNotFoundError`` on Windows) and nothing is written outside the
temporary directory.

This deliberately avoids expressing "unwritable" as a magic absolute path.
``/nonexistent/...`` was creatable by root in the CI container, and its
replacement ``/dev/null/...`` relies on ``/dev/null`` being a character
device, which is only true on POSIX -- on Windows it is an ordinary
relative path that ``os.makedirs`` happily creates at the drive root.
"""
with tempfile.TemporaryDirectory() as tmpdir:
blocker = os.path.join(tmpdir, "not-a-directory")
with open(blocker, "w", encoding="utf-8") as f:
f.write("placeholder")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the blocker file just needs to exist as a regular file for os.makedirs to fail below it — the contents are irrelevant. Path(blocker).touch() (or open(blocker, "w").close()) would convey that intent a little more directly than writing a literal "placeholder". Totally trivial; feel free to ignore.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — applied in 39d0082. blocker.touch() says "this only needs to exist" without the reader having to work out that the bytes are irrelevant.

Re-verified after the change, since the helper is the thing the whole PR rests on:

  • pytest tests/test_tool_output_budget_middleware.py — 124 passed on Linux, 124 passed on Windows 11
  • with except OSError: return None removed from _externalize, both test_returns_none_on_invalid_path and test_fallback_when_disk_write_fails still fail (NotADirectoryError on Linux, FileNotFoundError on Windows), so the tests still have teeth rather than passing for a new reason
  • ruff check / ruff format --check clean

Thanks for tracing it through _externalize yourself rather than taking the description's word for it.

yield os.path.join(blocker, "outputs")


def _tm(content: str = "ok", name: str = "tool", tool_call_id: str = "tc-1") -> ToolMessage:
return ToolMessage(content=content, name=name, tool_call_id=tool_call_id)

Expand Down Expand Up @@ -160,20 +183,15 @@ def test_writes_file_and_returns_virtual_path(self):
assert f.read() == "full content here"

def test_returns_none_on_invalid_path(self):
# ``/dev/null`` is a character device on both Linux and macOS, so
# ``os.makedirs`` cannot create any subdirectory under it for any
# user (including root). The previously-used ``/nonexistent/...``
# path was silently created by ``mkdir -p`` when the test process
# ran as root inside the CI container, which made this test fail
# in CI independently of the externalization logic under test.
path = _externalize(
"data",
tool_name="test",
tool_call_id="tc-1",
outputs_path="/dev/null/cannot-mkdir-here",
storage_subdir=".tool-results",
)
assert path is None
with _unwritable_outputs_path() as outputs_path:
path = _externalize(
"data",
tool_name="test",
tool_call_id="tc-1",
outputs_path=outputs_path,
storage_subdir=".tool-results",
)
assert path is None

def test_txt_extension_for_unknown_tool(self):
with tempfile.TemporaryDirectory() as tmpdir:
Expand Down Expand Up @@ -710,9 +728,10 @@ def test_fallback_when_disk_write_fails(self):
mw = ToolOutputBudgetMiddleware(config=config)
content = "x" * 500
msg = _tm(content, name="tool")
req = _make_request(outputs_path="/dev/null/cannot-mkdir-here")

result = mw.wrap_tool_call(req, lambda _: msg)
with _unwritable_outputs_path() as outputs_path:
req = _make_request(outputs_path=outputs_path)
result = mw.wrap_tool_call(req, lambda _: msg)

assert isinstance(result, ToolMessage)
assert "omitted from tool output" in result.content
Expand Down
Loading