ouroboros/tests/test_python_interpreter.py
Anton Razzhigaev 543cc8376c fix(test): 3-OS portability for the v6.67.0 suites
Windows: the python-resolver tests built fake venvs in POSIX layout
(bin/python) while project_venv_python correctly looks for
Scripts\python.exe — the fixture is now platform-aware; the isolated-checkout
test repos pin core.autocrlf=false so the LF staged patch applies in the
detached worktree on autocrlf=true runners. ui-smoke: the desktop chat scroll
probe re-injects its bubbles after the viewport resize dance — a resize can
re-render the chat from the (empty) real history and silently drop injected
nodes, which made the post-resize assertion an environment-dependent flake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 23:52:36 +00:00

488 lines
17 KiB
Python

from __future__ import annotations
import json
import pathlib
from types import SimpleNamespace
import pytest
from ouroboros.python_interpreter import resolve_process_python
from ouroboros.tools.registry import ToolContext, ToolRegistry
def _executable(path: pathlib.Path) -> pathlib.Path:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
path.chmod(0o755)
return path
def _venv_python(env_root: pathlib.Path) -> pathlib.Path:
"""Platform-correct fake venv interpreter (Scripts\\python.exe on Windows)."""
from ouroboros.platform_layer import IS_WINDOWS
if IS_WINDOWS:
return _executable(env_root / "Scripts" / "python.exe")
return _executable(env_root / "bin" / "python")
def _context(
tmp_path: pathlib.Path,
*,
workspace: pathlib.Path | None = None,
workspace_mode: str = "",
) -> ToolContext:
repo = tmp_path / "system_repo"
data = tmp_path / "data"
repo.mkdir(exist_ok=True)
data.mkdir(exist_ok=True)
if workspace is not None:
workspace.mkdir(parents=True, exist_ok=True)
return ToolContext(
repo_dir=repo,
system_repo_dir=repo,
drive_root=data,
workspace_root=workspace,
workspace_mode=workspace_mode,
task_id="python-resolver-test",
)
def _tool_args(tool_name: str, *, cwd: str = "", token: str = "python") -> dict:
if tool_name == "run_command":
return {"cmd": [token, "-V"], "cwd": cwd}
if tool_name == "run_script":
return {"script": "print('ok')", "interpreter": token, "cwd": cwd}
if tool_name == "start_service":
return {"name": "svc", "cmd": [token, "-V"], "cwd": cwd}
if tool_name == "verify_and_record":
return {
"contract_kind": "explicit_command",
"check": [token, "-V"],
"cwd": cwd,
}
raise AssertionError(tool_name)
def _interpreter(tool_name: str, args: dict) -> str:
if tool_name in {"run_command", "start_service"}:
return str(args["cmd"][0])
if tool_name == "run_script":
return str(args["interpreter"])
return str(args["check"][0])
@pytest.mark.parametrize(
"tool_name",
["run_command", "run_script", "start_service", "verify_and_record"],
)
def test_system_surfaces_use_validated_agent_python(tmp_path, monkeypatch, tool_name):
ctx = _context(tmp_path)
agent_python = _executable(tmp_path / "agent" / "bin" / "python")
monkeypatch.setenv("OUROBOROS_AGENT_PYTHON", str(agent_python))
resolved, trace = resolve_process_python(
ctx,
tool_name,
_tool_args(tool_name),
runtime_mode="advanced",
)
assert _interpreter(tool_name, resolved) == str(agent_python)
assert trace is not None
assert trace.surface == "system_repo"
assert trace.environment == "ouroboros_agent"
@pytest.mark.parametrize(
"tool_name",
["run_command", "run_script", "start_service", "verify_and_record"],
)
def test_external_workspace_prefers_project_venv(tmp_path, monkeypatch, tool_name):
workspace = tmp_path / "workspace"
ctx = _context(tmp_path, workspace=workspace, workspace_mode="external")
project_python = _venv_python(workspace / ".venv")
(workspace / ".venv" / "pyvenv.cfg").write_text("home = /usr/bin\n", encoding="utf-8")
monkeypatch.setenv(
"OUROBOROS_AGENT_PYTHON",
str(_executable(tmp_path / "agent" / "bin" / "python")),
)
resolved, trace = resolve_process_python(
ctx,
tool_name,
_tool_args(tool_name),
runtime_mode="advanced",
)
assert _interpreter(tool_name, resolved) == str(project_python)
assert trace is not None
assert trace.surface == "external_workspace"
assert trace.environment == "project_venv"
def test_project_venv_platform_layouts(tmp_path, monkeypatch):
from ouroboros import platform_layer
env_root = tmp_path / ".venv"
env_root.mkdir()
(env_root / "pyvenv.cfg").write_text("home = test\n", encoding="utf-8")
posix_python = env_root / "bin" / "python"
posix_python.parent.mkdir()
posix_python.write_text("", encoding="utf-8")
posix_python.chmod(0o755)
monkeypatch.setattr(platform_layer, "IS_WINDOWS", False)
assert platform_layer.project_venv_python(tmp_path) == str(posix_python)
windows_python = env_root / "Scripts" / "python.exe"
windows_python.parent.mkdir()
windows_python.write_text("", encoding="utf-8")
windows_python.chmod(0o755)
monkeypatch.setattr(platform_layer, "IS_WINDOWS", True)
assert platform_layer.project_venv_python(tmp_path) == str(windows_python)
def test_user_files_project_venv_and_missing_venv_path_fallback(tmp_path, monkeypatch):
user_root = tmp_path / "user_files"
project = user_root / "project"
project.mkdir(parents=True)
monkeypatch.setenv("OUROBOROS_USER_FILES_ROOT", str(user_root))
ctx = _context(tmp_path)
project_python = _venv_python(project / ".venv")
(project / ".venv" / "pyvenv.cfg").write_text("home = /usr/bin\n", encoding="utf-8")
resolved, trace = resolve_process_python(
ctx,
"run_command",
_tool_args("run_command", cwd=str(project)),
runtime_mode="advanced",
)
assert resolved["cmd"][0] == str(project_python)
assert trace is not None and trace.surface == "user_files"
(project / ".venv" / "pyvenv.cfg").unlink()
unresolved, fallback = resolve_process_python(
ctx,
"run_command",
_tool_args("run_command", cwd=str(project)),
runtime_mode="advanced",
)
assert unresolved["cmd"][0] == "python"
assert fallback is not None
assert fallback.environment == "target_path"
assert fallback.fallback_reason == "project_venv_unavailable"
def test_executor_uses_backend_python_but_unmapped_task_drive_uses_agent(tmp_path, monkeypatch):
workspace = tmp_path / "workspace"
ctx = _context(tmp_path, workspace=workspace, workspace_mode="external")
ctx.executor_ref = {
"type": "docker_exec",
"id": "programbench",
"container_name": "programbench",
"network": "none",
"workspace_host_path": str(workspace),
"workspace_backend_path": "/workspace",
}
_venv_python(workspace / ".venv")
(workspace / ".venv" / "pyvenv.cfg").write_text("home = /usr/bin\n", encoding="utf-8")
agent_python = _executable(tmp_path / "agent" / "bin" / "python")
monkeypatch.setenv("OUROBOROS_AGENT_PYTHON", str(agent_python))
resolved, trace = resolve_process_python(
ctx,
"run_command",
_tool_args("run_command"),
runtime_mode="advanced",
)
assert resolved["cmd"][0] == "python3"
assert trace is not None and trace.environment == "backend_path"
local, local_trace = resolve_process_python(
ctx,
"run_command",
_tool_args("run_command", cwd="task_drive"),
runtime_mode="advanced",
)
assert local["cmd"][0] == str(agent_python)
assert local_trace is not None and local_trace.surface == "task_drive"
def test_reviewed_skill_environment_precedes_executor(tmp_path, monkeypatch):
import ouroboros.marketplace.isolated_deps as isolated_deps
import ouroboros.skill_loader as skill_loader
import ouroboros.skill_readiness as skill_readiness
workspace = tmp_path / "workspace"
ctx = _context(tmp_path, workspace=workspace, workspace_mode="external")
ctx.task_metadata = {"source": "skill_scheduled_task", "skill": "demo"}
ctx.executor_ref = {
"type": "docker_exec",
"id": "executor",
"container_name": "executor",
"network": "none",
"workspace_host_path": str(workspace),
"workspace_backend_path": "/workspace",
}
skill_dir = tmp_path / "skill"
skill_dir.mkdir()
skill_python = _executable(skill_dir / ".ouroboros_env" / "python" / "bin" / "python")
loaded = SimpleNamespace(name="demo", skill_dir=skill_dir)
monkeypatch.setattr(skill_loader, "find_skill", lambda *args, **kwargs: loaded)
monkeypatch.setattr(
skill_readiness,
"skill_readiness_for_execution",
lambda *args, **kwargs: SimpleNamespace(ready=True),
)
monkeypatch.setattr(isolated_deps, "read_deps_state", lambda *args, **kwargs: {"status": "installed"})
monkeypatch.setattr(isolated_deps, "python_runtime_binary", lambda *args, **kwargs: skill_python)
resolved, trace = resolve_process_python(
ctx,
"run_command",
_tool_args("run_command"),
runtime_mode="advanced",
)
assert resolved["cmd"][0] == str(skill_python)
assert trace is not None
assert trace.environment == "isolated_skill"
@pytest.mark.parametrize(
("tool_name", "args"),
[
("run_command", {"cmd": ["/usr/bin/python", "-V"]}),
("run_command", {"cmd": ["python3.12", "-V"]}),
("run_command", {"cmd": ["sh", "-c", "python -V"]}),
("run_command", {"cmd": ["env", "python", "-V"]}),
("run_script", {"script": "pass", "interpreter": "/usr/bin/python"}),
("run_script", {"script": "pass", "interpreter": "python3.12"}),
("verify_and_record", {"contract_kind": "explicit_command", "check": "python -V"}),
("verify_and_record", {"contract_kind": "artifact_observation", "check": ["python", "-V"]}),
("remote_exec", {"cmd": ["python", "-V"]}),
],
)
def test_noneligible_invocations_are_byte_for_byte_unchanged(tmp_path, tool_name, args):
ctx = _context(tmp_path)
resolved, trace = resolve_process_python(
ctx,
tool_name,
args,
runtime_mode="advanced",
)
assert resolved == args
assert trace is None
def test_light_run_script_default_cwd_uses_task_drive_agent_python(tmp_path, monkeypatch):
ctx = _context(tmp_path)
agent_python = _executable(tmp_path / "agent" / "bin" / "python")
monkeypatch.setenv("OUROBOROS_AGENT_PYTHON", str(agent_python))
resolved, trace = resolve_process_python(
ctx,
"run_script",
{"script": "print('ok')"},
runtime_mode="light",
)
assert resolved["interpreter"] == str(agent_python)
assert trace is not None and trace.surface == "task_drive"
def test_registry_guard_and_handler_receive_same_resolved_verify_argv(tmp_path, monkeypatch):
import ouroboros.safety as safety
import ouroboros.tools.registry as registry_module
ctx = _context(tmp_path)
agent_python = _executable(tmp_path / "agent" / "bin" / "python")
monkeypatch.setenv("OUROBOROS_AGENT_PYTHON", str(agent_python))
monkeypatch.setenv("OUROBOROS_RUNTIME_MODE", "advanced")
monkeypatch.setattr(
safety,
"_run_llm_check",
lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("unexpected safety LLM call")),
)
registry = ToolRegistry(repo_dir=ctx.repo_dir, drive_root=ctx.drive_root)
registry.set_context(ctx)
captured: dict[str, list[str]] = {}
original_guard = registry_module.process_shell_guard_args
def capture_guard(name, args, **kwargs):
guarded = original_guard(name, args, **kwargs)
captured["guard"] = list(guarded["cmd"])
return guarded
def capture_handler(_ctx, contract_kind, check, **kwargs):
assert contract_kind == "explicit_command"
captured["handler"] = list(check)
return "ok"
monkeypatch.setattr(registry_module, "process_shell_guard_args", capture_guard)
monkeypatch.setattr(registry, "_run_shell_safety_check", lambda *args, **kwargs: "")
registry._entries["verify_and_record"].handler = capture_handler
result = registry.execute(
"verify_and_record",
{"contract_kind": "explicit_command", "check": ["python", "-m", "pytest", "--version"]},
)
expected = [str(agent_python), "-m", "pytest", "--version"]
assert result == "ok"
assert captured == {"guard": expected, "handler": expected}
events_path = ctx.drive_logs() / "events.jsonl"
event = json.loads(events_path.read_text(encoding="utf-8").splitlines()[-1])
assert event["type"] == "python_interpreter_resolution"
assert event["requested_interpreter"] == "python"
assert event["resolved_interpreter"] == str(agent_python)
assert "cmd" not in event
assert "check" not in event
def test_registry_uses_current_process_python_before_server_bootstrap(
tmp_path, monkeypatch,
):
import sys
ctx = _context(tmp_path)
monkeypatch.delenv("OUROBOROS_AGENT_PYTHON", raising=False)
monkeypatch.setenv("OUROBOROS_RUNTIME_MODE", "advanced")
monkeypatch.setattr("ouroboros.safety.check_safety", lambda *args, **kwargs: (True, ""))
registry = ToolRegistry(repo_dir=ctx.repo_dir, drive_root=ctx.drive_root)
registry.set_context(ctx)
observed = {}
def handler(_ctx, cmd, **_kwargs):
observed["cmd"] = cmd
return "ok"
registry._entries["run_command"].handler = handler
result = registry.execute("run_command", {"cmd": ["python", "-V"]})
assert result == "ok"
assert observed["cmd"][0] == str(pathlib.Path(sys.executable).absolute())
def test_run_script_accepts_registry_attested_versioned_agent_python(
tmp_path, monkeypatch,
):
import ouroboros.tools.shell as shell
ctx = _context(tmp_path)
versioned_python = _executable(tmp_path / "agent" / "bin" / "python3.12")
monkeypatch.setenv("OUROBOROS_AGENT_PYTHON", str(versioned_python))
monkeypatch.setenv("OUROBOROS_RUNTIME_MODE", "advanced")
monkeypatch.setattr("ouroboros.safety.check_safety", lambda *args, **kwargs: (True, ""))
monkeypatch.setattr(shell, "_run_shell", lambda *_args, **_kwargs: "ok")
registry = ToolRegistry(repo_dir=ctx.repo_dir, drive_root=ctx.drive_root)
registry.set_context(ctx)
monkeypatch.setattr(registry, "_run_shell_safety_check", lambda *args, **kwargs: "")
result = registry.execute(
"run_script",
{"script": "print('ok')", "interpreter": "python3"},
)
assert result.endswith("\nok")
assert "RUN_SCRIPT_BLOCKED" not in result
assert not hasattr(ctx, "_active_python_resolution")
def test_run_script_still_blocks_unattested_versioned_interpreter(tmp_path):
from ouroboros.tools.shell import _run_script
result = _run_script(
_context(tmp_path),
"print('unsafe')",
interpreter=str(tmp_path / "untrusted" / "python3.12"),
)
assert result.startswith("⚠️ RUN_SCRIPT_BLOCKED:")
def test_safety_fast_path_requires_matching_verified_resolver_provenance(tmp_path, monkeypatch):
import ouroboros.safety as safety
from ouroboros.python_interpreter import PythonResolutionTrace
agent_python = str(_executable(tmp_path / "agent" / "bin" / "python"))
verified = PythonResolutionTrace(
tool="run_command",
requested_interpreter="python",
resolved_interpreter=agent_python,
surface="system_repo",
environment="ouroboros_agent",
reason="agent_python",
)
calls: list[list[str]] = []
def llm_check(_tool, arguments, *_args, **_kwargs):
calls.append(list(arguments["cmd"]))
return False, "llm-called"
monkeypatch.setattr(safety, "_run_llm_check", llm_check)
safe = safety.check_safety(
"run_command",
{"cmd": [agent_python, "-m", "pytest", "-q"]},
python_resolution=verified,
)
arbitrary = safety.check_safety(
"run_command",
{"cmd": [agent_python, "-m", "pytest", "-q"]},
)
mismatch = safety.check_safety(
"run_command",
{"cmd": [str(tmp_path / "other" / "python"), "-m", "pytest", "-q"]},
python_resolution=verified,
)
assert safe == (True, "")
assert arbitrary == (False, "llm-called")
assert mismatch == (False, "llm-called")
assert len(calls) == 2
def test_verified_python_c_body_still_requires_safety_review(tmp_path, monkeypatch):
import ouroboros.safety as safety
from ouroboros.python_interpreter import PythonResolutionTrace
agent_python = str(_executable(tmp_path / "agent" / "bin" / "python"))
verified = PythonResolutionTrace(
tool="run_command",
requested_interpreter="python3",
resolved_interpreter=agent_python,
surface="system_repo",
environment="ouroboros_agent",
reason="agent_python",
)
monkeypatch.setattr(safety, "_run_llm_check", lambda *args, **kwargs: (False, "reviewed"))
result = safety.check_safety(
"run_command",
{"cmd": [agent_python, "-c", "print('not allowlisted by module')"]},
python_resolution=verified,
)
assert result == (False, "reviewed")
def test_resolution_trace_failure_is_fail_soft(tmp_path, monkeypatch):
import ouroboros.python_interpreter as resolver
trace = resolver.PythonResolutionTrace(
tool="run_command",
requested_interpreter="python",
resolved_interpreter="python3",
surface="executor",
environment="backend_path",
reason="executor_backend_python3",
)
monkeypatch.setattr(resolver, "append_jsonl", lambda *args, **kwargs: (_ for _ in ()).throw(OSError("full")))
resolver.record_python_resolution(_context(tmp_path), trace)