import json import os import subprocess from unittest.mock import MagicMock, patch import pytest from glas_harness.contract import Rein, SandboxHandle, ToolCall from glas_harness.profiles import ProfileCatalog from glas_harness.reins.rein_openweights import ReinOpenWeights, ReinOpenWeightsNotInstalled from glas_harness.transport import ExecutionTransport, TransportError def _init_repo(repo) -> None: repo.mkdir() subprocess.run(["git", "init", "-q"], cwd=repo, check=True) subprocess.run(["git", "config", "user.name", "Glas Harness Tests"], cwd=repo, check=True) subprocess.run( ["git", "config", "user.email", "glas-harness-tests@example.invalid"], cwd=repo, check=True, ) subprocess.run(["git", "commit", "-q", "--allow-empty", "-m", "init"], cwd=repo, check=True) def test_is_rein_subclass() -> None: assert issubclass(ReinOpenWeights, Rein) def test_bin_raises_if_not_installed() -> None: rein = ReinOpenWeights(cli_bin="does-not-exist-binary") transport = MagicMock(spec=ExecutionTransport) transport.resolve_executable.side_effect = TransportError("missing") with pytest.raises(ReinOpenWeightsNotInstalled): rein._bin(transport) def test_start_session_writes_task_file_and_captures_head(tmp_path) -> None: repo = tmp_path / "repo" _init_repo(repo) rein = ReinOpenWeights() sandbox = SandboxHandle( sandbox_id="abc", host="localhost", reachability={"pid": str(os.getpid()), "workspace_dir": str(repo)}, ) sandbox._owner_execute = MagicMock( return_value=MagicMock(timed_out=False, output_truncated=False, exit_code=0, stdout="", stderr="") ) with patch.object(ExecutionTransport, "git_head", return_value="abc123"): session = rein.start_session( profile=ProfileCatalog().resolve("harness.agent-dev-openweights-local@1.0.0")[0], inputs={"title": "t", "description": "d"}, sandbox=sandbox, ) assert session["target_repo"] == str(repo) assert session["head_before"] == "abc123" assert session["transport"].workspace == str(repo) task_spec = json.loads(sandbox._owner_execute.call_args.args[1]) assert task_spec["title"] == "t" assert task_spec["target_repo"] == str(repo) rein.cleanup_session(session) assert sandbox._owner_execute.call_args.args[0] == ["rm", "-f", "--", session["task_file"]] def test_start_session_requires_resolvable_target_repo() -> None: rein = ReinOpenWeights() sandbox = SandboxHandle(sandbox_id="abc", host="localhost", reachability={}) with pytest.raises(RuntimeError, match="reachability"): rein.start_session(profile={}, inputs={"title": "t", "description": "d"}, sandbox=sandbox) def test_dispatch_tool_invokes_rein_openweights_cli() -> None: rein = ReinOpenWeights() transport = MagicMock(spec=ExecutionTransport) transport.run.return_value = MagicMock(returncode=0, stdout="ok", stderr="") session = {"task_file": "/tmp/task.json", "transport": transport, "timeout_seconds": 30} with patch.object(rein, "_bin", return_value="/usr/bin/rein-openweights"): result = rein.dispatch_tool(session, ToolCall(name="run_task")) argv = transport.run.call_args.args[0] assert argv[:3] == ["/usr/bin/rein-openweights", "run", "--task-file"] assert "--no-hub" in argv assert result.ok is True assert result.output == "ok" def test_dispatch_tool_passes_model_when_set() -> None: rein = ReinOpenWeights( model="meta-llama/llama-3.1-70b-instruct", max_turns=8, budget_tokens=1234, tool_profile="green-commit-only", ) transport = MagicMock(spec=ExecutionTransport) transport.run.return_value = MagicMock(returncode=0, stdout="ok", stderr="") session = {"task_file": "/tmp/task.json", "transport": transport, "timeout_seconds": 30} with patch.object(rein, "_bin", return_value="/usr/bin/rein-openweights"): rein.dispatch_tool(session, ToolCall(name="run_task")) argv = transport.run.call_args.args[0] assert "--model" in argv assert "meta-llama/llama-3.1-70b-instruct" in argv assert argv[argv.index("--max-turns") + 1] == "8" assert argv[argv.index("--budget-tokens") + 1] == "1234" assert argv[argv.index("--tool-profile") + 1] == "green-commit-only" def test_dispatch_tool_reports_failure() -> None: rein = ReinOpenWeights() transport = MagicMock(spec=ExecutionTransport) transport.run.return_value = MagicMock(returncode=1, stdout="", stderr="boom") session = {"task_file": "/tmp/task.json", "transport": transport, "timeout_seconds": 30} with patch.object(rein, "_bin", return_value="/usr/bin/rein-openweights"): result = rein.dispatch_tool(session, ToolCall(name="run_task")) assert result.ok is False assert result.error == "boom" def test_dispatch_tool_enforces_outer_timeout() -> None: rein = ReinOpenWeights() transport = MagicMock(spec=ExecutionTransport) transport.run.side_effect = subprocess.TimeoutExpired("rein-openweights", 30) session = { "task_file": "/tmp/task.json", "transport": transport, "timeout_seconds": 30, } with patch.object(rein, "_bin", return_value="/usr/bin/rein-openweights"): with pytest.raises(subprocess.TimeoutExpired): rein.dispatch_tool(session, ToolCall(name="run_task")) assert transport.run.call_args.kwargs["timeout"] == 30 def test_end_session_detects_new_commit(tmp_path) -> None: repo = tmp_path / "repo" _init_repo(repo) rein = ReinOpenWeights() head_before = subprocess.run( ["git", "-C", str(repo), "rev-parse", "HEAD"], capture_output=True, text=True, check=True, ).stdout.strip() subprocess.run(["git", "commit", "-q", "--allow-empty", "-m", "task"], cwd=repo, check=True) transport = MagicMock(spec=ExecutionTransport) transport.git_head.return_value = subprocess.run( ["git", "-C", str(repo), "rev-parse", "HEAD"], capture_output=True, text=True, check=True, ).stdout.strip() summary = rein.end_session({"transport": transport, "head_before": head_before}) assert summary.committed is True assert summary.outcome == "succeeded" assert summary.commit_sha != head_before def test_cleanup_session_removes_generated_task_file() -> None: rein = ReinOpenWeights() transport = MagicMock(spec=ExecutionTransport) rein.cleanup_session( {"transport": transport, "task_file": "/sandbox/task.json", "generated_task_file": True} ) transport.remove_file.assert_called_once_with("/sandbox/task.json")