Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
"""Startup-latency regressions: probe-mode aux clients, lazy MCP SDK,
|
||||
non-blocking banner update check.
|
||||
|
||||
These pin the CLI cold-start contract established in the sub-400ms pass:
|
||||
- check_fn availability probes must not import the OpenAI SDK or build
|
||||
real HTTP clients (aux_probe_mode).
|
||||
- tools/mcp_tool must not import the `mcp` SDK at module import time.
|
||||
- build_welcome_banner must not block on the update-check prefetch.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestAuxProbeMode:
|
||||
def test_probe_mode_returns_stub_without_openai_import(self):
|
||||
import agent.auxiliary_client as aux
|
||||
|
||||
with aux.aux_probe_mode():
|
||||
client = aux._create_openai_client(api_key="k", base_url="https://x.invalid/v1")
|
||||
assert isinstance(client, aux._AuxProbeClientStub)
|
||||
assert client.api_key == "k"
|
||||
|
||||
def test_probe_stub_never_cached(self):
|
||||
import agent.auxiliary_client as aux
|
||||
|
||||
stub = aux._AuxProbeClientStub()
|
||||
key = ("probe-test", False, "", "", "", (), False, "", None, "m")
|
||||
aux._store_cached_client(key, stub, "m")
|
||||
with aux._client_cache_lock:
|
||||
assert key not in aux._client_cache
|
||||
|
||||
def test_probe_stub_raises_on_runtime_use(self):
|
||||
import agent.auxiliary_client as aux
|
||||
|
||||
stub = aux._AuxProbeClientStub()
|
||||
with pytest.raises(RuntimeError, match="availability checks only"):
|
||||
_ = stub.chat
|
||||
|
||||
def test_probe_mode_is_scoped_and_reentrant(self):
|
||||
import agent.auxiliary_client as aux
|
||||
|
||||
assert not aux._aux_probe_active()
|
||||
with aux.aux_probe_mode():
|
||||
assert aux._aux_probe_active()
|
||||
with aux.aux_probe_mode():
|
||||
assert aux._aux_probe_active()
|
||||
# inner exit must not clear the outer scope
|
||||
assert aux._aux_probe_active()
|
||||
assert not aux._aux_probe_active()
|
||||
|
||||
def test_probe_mode_is_thread_local(self):
|
||||
import agent.auxiliary_client as aux
|
||||
|
||||
seen = {}
|
||||
|
||||
def other_thread():
|
||||
seen["active"] = aux._aux_probe_active()
|
||||
|
||||
with aux.aux_probe_mode():
|
||||
t = threading.Thread(target=other_thread)
|
||||
t.start()
|
||||
t.join()
|
||||
assert seen["active"] is False
|
||||
|
||||
def test_maybe_wrap_anthropic_passes_stub_through(self):
|
||||
import agent.auxiliary_client as aux
|
||||
|
||||
stub = aux._AuxProbeClientStub(base_url="https://api.anthropic.com")
|
||||
out = aux._maybe_wrap_anthropic(stub, "m", "key", "https://api.anthropic.com")
|
||||
assert out is stub
|
||||
|
||||
def test_to_async_client_passes_stub_through(self):
|
||||
import agent.auxiliary_client as aux
|
||||
|
||||
stub = aux._AuxProbeClientStub()
|
||||
client, model = aux._to_async_client(stub, "m")
|
||||
assert client is stub
|
||||
assert model == "m"
|
||||
|
||||
|
||||
class TestVisionCheckUsesProbeMode:
|
||||
def test_check_vision_requirements_enters_probe_mode(self):
|
||||
from tools import vision_tools
|
||||
import agent.auxiliary_client as aux
|
||||
|
||||
states = []
|
||||
|
||||
def fake_resolver(*a, **k):
|
||||
states.append(aux._aux_probe_active())
|
||||
return ("nous", aux._AuxProbeClientStub(), "m")
|
||||
|
||||
with patch.object(aux, "resolve_vision_provider_client", fake_resolver):
|
||||
assert vision_tools.check_vision_requirements() is True
|
||||
assert states and all(states)
|
||||
|
||||
|
||||
class TestLazyMcpSdk:
|
||||
def test_module_import_does_not_import_mcp_sdk(self):
|
||||
"""Importing tools.mcp_tool must not pull in the `mcp` package."""
|
||||
import subprocess
|
||||
|
||||
code = (
|
||||
"import sys; sys.modules.pop('mcp', None); "
|
||||
"import tools.mcp_tool; "
|
||||
"assert 'mcp' not in sys.modules, 'mcp imported eagerly'; "
|
||||
"print('ok')"
|
||||
)
|
||||
proc = subprocess.run(
|
||||
[sys.executable, "-c", code],
|
||||
capture_output=True, text=True, timeout=120,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
assert "ok" in proc.stdout
|
||||
|
||||
def test_availability_flag_reflects_find_spec(self):
|
||||
import importlib.util
|
||||
from tools import mcp_tool
|
||||
|
||||
expected = importlib.util.find_spec("mcp") is not None
|
||||
assert mcp_tool._MCP_AVAILABLE is expected
|
||||
|
||||
def test_ensure_mcp_sdk_binds_symbols(self):
|
||||
import importlib.util
|
||||
from tools import mcp_tool
|
||||
|
||||
if importlib.util.find_spec("mcp") is None:
|
||||
pytest.skip("mcp SDK not installed")
|
||||
assert mcp_tool._ensure_mcp_sdk() is True
|
||||
assert mcp_tool.ClientSession is not None
|
||||
assert mcp_tool.stdio_client is not None
|
||||
|
||||
def test_ensure_respects_patched_unavailable(self):
|
||||
from tools import mcp_tool
|
||||
|
||||
with patch.object(mcp_tool, "_MCP_AVAILABLE", False):
|
||||
assert mcp_tool._ensure_mcp_sdk() is False
|
||||
|
||||
def test_lazy_symbol_getattr_resolves_via_ensure(self):
|
||||
import importlib.util
|
||||
from tools import mcp_tool
|
||||
|
||||
if importlib.util.find_spec("mcp") is None:
|
||||
pytest.skip("mcp SDK not installed")
|
||||
# getattr through the module (what mock.patch does when saving the
|
||||
# original) must materialize the symbol instead of AttributeError.
|
||||
assert getattr(mcp_tool, "StdioServerParameters") is not None
|
||||
|
||||
|
||||
class TestBannerUpdateCheckNonBlocking:
|
||||
def test_banner_does_not_block_on_pending_update_check(self):
|
||||
"""When the prefetch hasn't finished, the banner path must return in
|
||||
well under the old 500ms blocking wait."""
|
||||
import hermes_cli.banner as banner
|
||||
|
||||
class _NullConsole:
|
||||
def print(self, *a, **k):
|
||||
pass
|
||||
|
||||
with patch.object(banner, "_update_check_done", threading.Event()), \
|
||||
patch.object(banner, "_deferred_update_notice_started", False):
|
||||
start = time.perf_counter()
|
||||
behind = banner.get_update_result(timeout=0.05)
|
||||
if behind is None and not banner._update_check_done.is_set():
|
||||
banner._defer_update_notice(_NullConsole())
|
||||
elapsed = time.perf_counter() - start
|
||||
assert elapsed < 0.3, f"banner update check blocked {elapsed:.3f}s"
|
||||
|
||||
def test_deferred_notice_prints_when_result_lands(self):
|
||||
import hermes_cli.banner as banner
|
||||
|
||||
printed = []
|
||||
|
||||
class _Console:
|
||||
def print(self, msg, *a, **k):
|
||||
printed.append(msg)
|
||||
|
||||
done = threading.Event()
|
||||
with patch.object(banner, "_update_check_done", done), \
|
||||
patch.object(banner, "_update_result", None), \
|
||||
patch.object(banner, "_deferred_update_notice_started", False):
|
||||
banner._defer_update_notice(_Console(), max_wait=5.0)
|
||||
banner._update_result = 3
|
||||
done.set()
|
||||
deadline = time.time() + 5
|
||||
while not printed and time.time() < deadline:
|
||||
time.sleep(0.02)
|
||||
assert printed, "deferred update notice never printed"
|
||||
assert "3 commits behind" in printed[0]
|
||||
|
||||
def test_deferred_notice_silent_when_up_to_date(self):
|
||||
import hermes_cli.banner as banner
|
||||
|
||||
printed = []
|
||||
|
||||
class _Console:
|
||||
def print(self, msg, *a, **k):
|
||||
printed.append(msg)
|
||||
|
||||
done = threading.Event()
|
||||
with patch.object(banner, "_update_check_done", done), \
|
||||
patch.object(banner, "_update_result", 0), \
|
||||
patch.object(banner, "_deferred_update_notice_started", False):
|
||||
banner._defer_update_notice(_Console(), max_wait=2.0)
|
||||
done.set()
|
||||
time.sleep(0.3)
|
||||
assert not printed
|
||||
Reference in New Issue
Block a user