mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 04:07:04 +00:00
Repair cross-platform test isolation and retain Docker failure evidence
Use short pytest labels without reducing large Unicode inputs, block provider/catalog calls at their existing seams instead of OS-local sockets, and decode UTF-8 task-state fixtures explicitly. Retain browser/container facts on Docker UI failure without changing the acceptance condition or timeout. Seven test files only; production behavior is unchanged. Focused combined Windows-related modules: 72 passed. Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
This commit is contained in:
parent
081a9d3886
commit
cc7269a5d5
7 changed files with 65 additions and 24 deletions
|
|
@ -34,7 +34,7 @@ def _record(root, raw, *, closed=True, findings=None, evidence_manifest=None):
|
|||
|
||||
|
||||
def _raw_state(root, task_id="large-plan"):
|
||||
return json.loads(task_results.task_result_path(root, task_id).read_text())["plan_review_state"]
|
||||
return json.loads(task_results.task_result_path(root, task_id).read_text(encoding="utf-8"))["plan_review_state"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("field", ["goal", "acceptance_claims", "in_scope"])
|
||||
|
|
|
|||
|
|
@ -763,7 +763,7 @@ def test_an_ordinary_contract_field_reaches_the_run_instructions_complete(tmp_pa
|
|||
@pytest.mark.parametrize("objective", [
|
||||
"UNIQUE_ATOMIC_OBJECTIVE",
|
||||
"яё𐍈🚀\n" * 55_000 + "LARGE_COMPILED_OBJECTIVE_TAIL",
|
||||
])
|
||||
], ids=["short", "large-unicode"])
|
||||
def test_atomic_compiled_work_order_sends_dynamic_brief_once(tmp_path, monkeypatch, objective):
|
||||
from ouroboros.subagent_work_order import compile_external_work_order
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@ def test_answer_frame_describes_only_work_that_continued(required):
|
|||
def test_expired_wait_clock_precedes_post_tool_budget(tmp_path, monkeypatch, cold):
|
||||
"""A saved tail may have spent its budget, but an expired clock keeps its cause."""
|
||||
import json
|
||||
import socket
|
||||
import time
|
||||
from ouroboros import loop, model_wait, owner_wait, task_pacing
|
||||
from tests.test_owner_wait_cold_loop import cold_registry
|
||||
|
|
@ -109,8 +108,9 @@ def test_expired_wait_clock_precedes_post_tool_budget(tmp_path, monkeypatch, col
|
|||
|
||||
def no_network(*_args, **_kwargs):
|
||||
pytest.fail("synthetic wait must never contact a provider")
|
||||
monkeypatch.setattr(socket.socket, "connect", no_network)
|
||||
monkeypatch.setattr(socket.socket, "connect_ex", no_network)
|
||||
monkeypatch.setattr("ouroboros.llm.LLMClient.chat", no_network)
|
||||
monkeypatch.setattr("ouroboros.llm.LLMClient.chat_async", no_network)
|
||||
monkeypatch.setattr("ouroboros.pricing._fetch_live_rows", no_network)
|
||||
monkeypatch.setenv("OUROBOROS_TASK_ABS_CEILING_SEC", "21600")
|
||||
registry = cold_registry(tmp_path, monkeypatch, task_pacing.CostCeiling(state="active", ceiling_usd=9.0))
|
||||
ctx = registry._ctx
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"""A cold grant finishes the saved round before another ordinary model/tool call."""
|
||||
|
||||
import json
|
||||
import socket
|
||||
from dataclasses import replace
|
||||
|
||||
import pytest
|
||||
|
|
@ -30,9 +29,9 @@ def test_cold_grant_checks_saved_budget_before_ordinary_dispatch(tmp_path, monke
|
|||
accounting.settle_attempt(held, {"prompt_tokens": 1, "completion_tokens": 1},
|
||||
cost_usd=amount, cost_final=True)
|
||||
|
||||
def refuse_network(*args):
|
||||
network.append(args)
|
||||
raise AssertionError("fixture attempted network access")
|
||||
def refuse_network(*args, **kwargs):
|
||||
network.append((args, kwargs))
|
||||
raise AssertionError("fixture attempted a provider call")
|
||||
|
||||
def forced(call, **kwargs):
|
||||
calls.append((phase, "forced", call.round_idx, call.active_model))
|
||||
|
|
@ -55,8 +54,8 @@ def test_cold_grant_checks_saved_budget_before_ordinary_dispatch(tmp_path, monke
|
|||
return {"role": "assistant", "content": "", "tool_calls": [{"id": phase, "type": "function",
|
||||
"function": {"name": name, "arguments": json.dumps(args)}}]}, .2
|
||||
|
||||
monkeypatch.setattr(socket.socket, "connect", refuse_network)
|
||||
monkeypatch.setattr(socket.socket, "connect_ex", refuse_network)
|
||||
monkeypatch.setattr("ouroboros.llm.LLMClient.chat", refuse_network)
|
||||
monkeypatch.setattr("ouroboros.llm.LLMClient.chat_async", refuse_network)
|
||||
monkeypatch.setattr(pricing, "_fetch_live_rows", lambda provider: {})
|
||||
monkeypatch.setattr(loop, "_dispatch_round_model", ordinary)
|
||||
monkeypatch.setattr(loop, "_call_forced_model_once", forced)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""Cold continuation reaches the real loop with its original route and ceiling."""
|
||||
|
||||
import asyncio
|
||||
from dataclasses import replace
|
||||
import json
|
||||
import socket
|
||||
|
|
@ -21,19 +22,27 @@ from tests.test_loop_transport_wait import _loop_kwargs
|
|||
|
||||
@pytest.fixture(autouse=True)
|
||||
def no_network(monkeypatch):
|
||||
"""These synthetic model/route fixtures must never reach a provider or catalog."""
|
||||
"""Block provider/catalog calls, leaving asyncio's local IPC available."""
|
||||
attempted = []
|
||||
|
||||
def refuse_connect(_socket, address):
|
||||
attempted.append(address)
|
||||
raise AssertionError("cold-loop fixture attempted network access")
|
||||
def refuse_provider(*args, **kwargs):
|
||||
attempted.append((args, kwargs))
|
||||
raise AssertionError("cold-loop fixture attempted a provider/catalog call")
|
||||
|
||||
monkeypatch.setattr(socket.socket, "connect", refuse_connect)
|
||||
monkeypatch.setattr(socket.socket, "connect_ex", refuse_connect)
|
||||
monkeypatch.setattr("ouroboros.llm.LLMClient.chat", refuse_provider)
|
||||
monkeypatch.setattr("ouroboros.llm.LLMClient.chat_async", refuse_provider)
|
||||
monkeypatch.setattr("ouroboros.pricing._fetch_live_rows", refuse_provider)
|
||||
yield
|
||||
assert attempted == []
|
||||
|
||||
|
||||
def test_provider_isolation_preserves_event_loop_self_pipe(monkeypatch):
|
||||
# Windows uses this TCP fallback for asyncio's internal wakeup channel.
|
||||
monkeypatch.setattr(socket, "socketpair", socket._fallback_socketpair)
|
||||
event_loop = asyncio.new_event_loop()
|
||||
event_loop.close()
|
||||
|
||||
|
||||
def cold_registry(tmp_path, monkeypatch, ceiling=None):
|
||||
registry = ToolRegistry(repo_dir=tmp_path, drive_root=tmp_path)
|
||||
ctx = registry._ctx
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import copy
|
|||
from dataclasses import replace
|
||||
import json
|
||||
import queue as stdqueue
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
from types import SimpleNamespace
|
||||
|
|
@ -24,14 +23,16 @@ from tests.test_loop_transport_wait import _loop_kwargs
|
|||
|
||||
@pytest.fixture(autouse=True)
|
||||
def no_network(monkeypatch):
|
||||
"""Block provider/catalog calls without interfering with local event-loop IPC."""
|
||||
attempted = []
|
||||
|
||||
def refuse(*args):
|
||||
attempted.append(args)
|
||||
raise AssertionError("owner-wait model-state fixture attempted network")
|
||||
def refuse(*args, **kwargs):
|
||||
attempted.append((args, kwargs))
|
||||
raise AssertionError("owner-wait model-state fixture attempted a provider/catalog call")
|
||||
|
||||
monkeypatch.setattr(socket.socket, "connect", refuse)
|
||||
monkeypatch.setattr(socket.socket, "connect_ex", refuse)
|
||||
monkeypatch.setattr("ouroboros.llm.LLMClient.chat", refuse)
|
||||
monkeypatch.setattr("ouroboros.llm.LLMClient.chat_async", refuse)
|
||||
monkeypatch.setattr("ouroboros.pricing._fetch_live_rows", refuse)
|
||||
monkeypatch.setenv("OUROBOROS_CONTEXT_MODE", "max")
|
||||
monkeypatch.setenv("OUROBOROS_TASK_REVIEW_MODE", "off")
|
||||
monkeypatch.setenv("OUROBOROS_TASK_ABS_CEILING_SEC", "21600")
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import sys
|
|||
import textwrap
|
||||
import time
|
||||
import urllib.request
|
||||
from collections import deque
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -189,14 +190,36 @@ def _run_docker_ui_assertions(url: str) -> None:
|
|||
with sync_playwright() as pw:
|
||||
browser = pw.chromium.launch(headless=True)
|
||||
page = browser.new_page(viewport={"width": 390, "height": 844})
|
||||
events = deque(maxlen=40)
|
||||
page.on("console", lambda msg: events.append(["console", msg.type, msg.text[:1000]]))
|
||||
page.on("pageerror", lambda error: events.append(["pageerror", str(error)[:1000]]))
|
||||
page.on("requestfailed", lambda req: events.append(["requestfailed", req.url, req.failure]))
|
||||
page.on("response", lambda res: events.append(["response", res.status, res.url])
|
||||
if res.request.resource_type in {"document", "script"} or res.status >= 400 else None)
|
||||
response = None
|
||||
try:
|
||||
page.goto(url, wait_until="domcontentloaded", timeout=30_000)
|
||||
response = page.goto(url, wait_until="domcontentloaded", timeout=30_000)
|
||||
if page.locator("#onboarding-overlay").count():
|
||||
overlay_text = page.locator("#onboarding-overlay").inner_text(timeout=5_000)
|
||||
if "Ouroboros" in overlay_text:
|
||||
return
|
||||
page.wait_for_selector("#page-chat", timeout=30_000)
|
||||
assert page.locator("#page-chat").count() == 1
|
||||
except Exception:
|
||||
print("DOCKER_UI_BROWSER_EVENTS " + json.dumps(list(events)), file=sys.stderr)
|
||||
try:
|
||||
print("DOCKER_UI_PAGE " + json.dumps({
|
||||
"url": page.url, "title": page.title(),
|
||||
"document_status": response.status if response is not None else None,
|
||||
"nodes": {selector: {"count": page.locator(selector).count(),
|
||||
"visible": page.locator(selector).is_visible()}
|
||||
for selector in ("#page-chat", "#onboarding-overlay", ".onboarding-frame")},
|
||||
"body": page.locator("body").inner_text(timeout=1000)[:4000],
|
||||
"html": page.content()[:8000],
|
||||
}), file=sys.stderr)
|
||||
except Exception as diagnostic_error:
|
||||
print(f"DOCKER_UI_PAGE unavailable: {diagnostic_error}", file=sys.stderr)
|
||||
raise
|
||||
finally:
|
||||
browser.close()
|
||||
except PlaywrightError as exc:
|
||||
|
|
@ -2221,6 +2244,15 @@ def test_ui_smoke_docker_mode_loads_health():
|
|||
url = f"http://127.0.0.1:{port}"
|
||||
_wait_health(url, timeout_sec=45)
|
||||
_run_docker_ui_assertions(url)
|
||||
except Exception:
|
||||
try:
|
||||
logs = subprocess.run(["docker", "logs", "--tail", "100", cid],
|
||||
capture_output=True, text=True, timeout=10)
|
||||
print(f"DOCKER_UI_CONTAINER {cid} logs_exit={logs.returncode}\n"
|
||||
f"{logs.stdout[-10000:]}\n{logs.stderr[-10000:]}", file=sys.stderr)
|
||||
except Exception as diagnostic_error:
|
||||
print(f"DOCKER_UI_CONTAINER logs unavailable: {diagnostic_error}", file=sys.stderr)
|
||||
raise
|
||||
finally:
|
||||
subprocess.run(["docker", "stop", cid], capture_output=True, text=True, timeout=30)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue