Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""Measured-work pins for the slash-completer config reads.
|
||||
|
||||
The /tools and /personality completers run on every keystroke while the
|
||||
user types those commands (complete_while_typing). They used to re-read +
|
||||
re-parse the full config on every keypress: load_config()'s defensive
|
||||
deepcopy (~345us tax) in _tools_completions, and load_cli_config()'s full
|
||||
YAML parse + defaults deep-merge (~110us) in _personality_completions.
|
||||
These pins hold the per-keystroke cost down:
|
||||
- _tools_completions uses the read-only loader (no deepcopy).
|
||||
- _personality_completions memoises the personalities source keyed on the
|
||||
config file's mtime, so the parse+merge runs once per config state.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
import hermes_cli.commands as commands_mod
|
||||
|
||||
|
||||
def _reset_personalities_memo():
|
||||
commands_mod._personalities_memo = None
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_memo():
|
||||
_reset_personalities_memo()
|
||||
yield
|
||||
_reset_personalities_memo()
|
||||
|
||||
|
||||
class TestToolsCompletionsReadonlyConfig:
|
||||
def test_uses_readonly_loader(self):
|
||||
"""_tools_completions must not pay the defensive deepcopy.
|
||||
|
||||
The completer only reads the config (toolset enable state + MCP
|
||||
server names). Using load_config_readonly() skips the ~345us
|
||||
deepcopy that load_config() applies on every cache hit — a
|
||||
per-keystroke cost while completing /tools enable|disable.
|
||||
"""
|
||||
calls = {"deepcopy": 0, "readonly": 0}
|
||||
|
||||
def counting_deepcopy(*a, **k):
|
||||
calls["deepcopy"] += 1
|
||||
return {}
|
||||
|
||||
def counting_readonly(*a, **k):
|
||||
calls["readonly"] += 1
|
||||
return {}
|
||||
|
||||
# The completer imports the loader inside the function, so patch the
|
||||
# source module. Portable-MCP lookup is stubbed because it triggers
|
||||
# one-time plugin discovery (which legitimately calls load_config
|
||||
# during process init) — this test asserts on the completer's own
|
||||
# per-keystroke reads, not discovery's one-off startup reads.
|
||||
with patch("hermes_cli.config.load_config", counting_deepcopy), \
|
||||
patch("hermes_cli.config.load_config_readonly", counting_readonly), \
|
||||
patch("hermes_cli.plugins.get_portable_mcp_server_names_nowait", lambda: set()), \
|
||||
patch("hermes_cli.tools_config._get_plugin_toolset_keys", lambda: set()), \
|
||||
patch("hermes_cli.tools_config._homeassistant_credentials_present", lambda: False), \
|
||||
patch("hermes_cli.tools_config._xai_credentials_present", lambda: False):
|
||||
list(commands_mod.SlashCommandCompleter._tools_completions("enable ", "enable "))
|
||||
|
||||
assert calls["readonly"] == 1, "completer should use the readonly loader"
|
||||
assert calls["deepcopy"] == 0, (
|
||||
"completer must not call the deepcopy loader on a read-only path"
|
||||
)
|
||||
|
||||
|
||||
class TestPersonalityCompletionsMemo:
|
||||
def test_load_cli_config_called_once_per_config_state(self, monkeypatch):
|
||||
"""The /personality completer parses the config once per state.
|
||||
|
||||
load_cli_config() does a full YAML parse + deep merge of the
|
||||
built-in defaults; the completer runs on every keystroke. The
|
||||
mtime-keyed memo keeps that parse to once per config change.
|
||||
"""
|
||||
calls = {"n": 0}
|
||||
|
||||
def counting_load_cli_config():
|
||||
calls["n"] += 1
|
||||
return {
|
||||
"agent": {
|
||||
"personalities": {
|
||||
"helpful": "You are helpful.",
|
||||
"concise": "You are concise.",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
monkeypatch.setattr(commands_mod, "_personalities_memo", None)
|
||||
with patch("cli.load_cli_config", counting_load_cli_config):
|
||||
# First call: cache miss -> one parse.
|
||||
list(commands_mod.SlashCommandCompleter._personality_completions("hel", "hel"))
|
||||
assert calls["n"] == 1, "first call should parse once"
|
||||
|
||||
# Subsequent keystrokes: cache hit -> no re-parse.
|
||||
for _ in range(10):
|
||||
list(commands_mod.SlashCommandCompleter._personality_completions("hel", "hel"))
|
||||
assert calls["n"] == 1, (
|
||||
"repeated keystrokes must reuse the memoised personalities, "
|
||||
f"got {calls['n']} parses"
|
||||
)
|
||||
|
||||
def test_mtime_change_reparses(self, monkeypatch, tmp_path):
|
||||
"""A config file change on disk invalidates the memo."""
|
||||
from pathlib import Path
|
||||
|
||||
cfg_path = tmp_path / "config.yaml"
|
||||
cfg_path.write_text(
|
||||
"agent:\n personalities:\n helpful: \"v1\"\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
# Pin an explicit mtime so the change below is a guaranteed mtime_ns
|
||||
# bump regardless of filesystem timestamp granularity.
|
||||
os.utime(cfg_path, (1_700_000_000, 1_700_000_000))
|
||||
|
||||
calls = {"n": 0}
|
||||
|
||||
def counting_load_cli_config():
|
||||
calls["n"] += 1
|
||||
return {"agent": {"personalities": {"helpful": "v1"}}}
|
||||
|
||||
def fake_config_path():
|
||||
return cfg_path
|
||||
|
||||
monkeypatch.setattr(commands_mod, "_personalities_memo", None)
|
||||
with patch("cli.load_cli_config", counting_load_cli_config), \
|
||||
patch("hermes_cli.config.get_config_path", fake_config_path):
|
||||
list(commands_mod.SlashCommandCompleter._personality_completions("h", "h"))
|
||||
assert calls["n"] == 1
|
||||
|
||||
# Bump the file mtime -> memo invalidates -> re-parse once.
|
||||
os.utime(cfg_path, (1_800_000_000, 1_800_000_000))
|
||||
list(commands_mod.SlashCommandCompleter._personality_completions("h", "h"))
|
||||
assert calls["n"] == 2, "config mtime change should re-parse once"
|
||||
Reference in New Issue
Block a user