reachy_mini_conversation_rmscript / tests /test_external_loading.py
dlouapre's picture
dlouapre HF Staff
Rename conversation app to reachy_mini_conversation_rmscript standalone app
b461b14
Raw
History Blame Contribute Delete
7.42 kB
import sys
import importlib
from types import ModuleType
from pathlib import Path
import pytest
import reachy_mini_conversation_rmscript.config as config_mod
def _reload_core_tools() -> ModuleType:
"""Reload core_tools after config object has been patched."""
for module_name in list(sys.modules):
if module_name.startswith("reachy_mini_conversation_rmscript.tools."):
sys.modules.pop(module_name, None)
# External file-loaded modules are registered by bare tool name.
sys.modules.pop("ext_ping", None)
sys.modules.pop("sweep_look", None)
sys.modules.pop("ext_dup_a", None)
sys.modules.pop("ext_dup_b", None)
sys.modules.pop("reachy_mini_conversation_rmscript.tools.core_tools", None)
core_tools_mod = importlib.import_module("reachy_mini_conversation_rmscript.tools.core_tools")
core_tools_mod.initialize_tools()
return core_tools_mod
def test_external_profile_can_use_builtin_tools(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""External profile tools.txt can reference built-in src tools."""
profile_name = "ext_profile_test"
external_profiles_root = tmp_path / "external_profiles"
profile_dir = external_profiles_root / profile_name
profile_dir.mkdir(parents=True)
(profile_dir / "instructions.txt").write_text("hello\n", encoding="utf-8")
(profile_dir / "tools.txt").write_text("dance\n", encoding="utf-8")
monkeypatch.setattr(config_mod.config, "REACHY_MINI_CUSTOM_PROFILE", profile_name)
monkeypatch.setattr(config_mod.config, "PROFILES_DIRECTORY", external_profiles_root)
monkeypatch.setattr(config_mod.config, "TOOLS_DIRECTORY", None)
monkeypatch.setattr(config_mod.config, "AUTOLOAD_EXTERNAL_TOOLS", False)
core_tools_mod = _reload_core_tools()
assert "dance" in core_tools_mod.ALL_TOOLS
assert "dance" not in sys.modules
def test_external_tools_can_be_loaded_without_external_profile(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""External tools can be loaded with built-in profile via autoload mode."""
external_tools_root = tmp_path / "external_tools"
external_tools_root.mkdir(parents=True)
(external_tools_root / "ext_ping.py").write_text(
"\n".join(
[
"from typing import Any, Dict",
"from reachy_mini_conversation_rmscript.tools.core_tools import Tool, ToolDependencies",
"",
"class ExtPingTool(Tool):",
' name = "ext_ping"',
' description = "External ping tool"',
' parameters_schema = {"type": "object", "properties": {}, "required": []}',
"",
" async def __call__(self, deps: ToolDependencies, **kwargs: Any) -> Dict[str, Any]:",
' return {"status": "ok"}',
"",
]
),
encoding="utf-8",
)
monkeypatch.setattr(config_mod.config, "REACHY_MINI_CUSTOM_PROFILE", "default")
monkeypatch.setattr(config_mod.config, "PROFILES_DIRECTORY", config_mod.DEFAULT_PROFILES_DIRECTORY)
monkeypatch.setattr(config_mod.config, "TOOLS_DIRECTORY", external_tools_root)
monkeypatch.setattr(config_mod.config, "AUTOLOAD_EXTERNAL_TOOLS", True)
core_tools_mod = _reload_core_tools()
assert "ext_ping" in core_tools_mod.ALL_TOOLS
def test_external_tools_fail_on_duplicate_tool_names(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""Loading must fail if multiple tools declare the same Tool.name."""
external_tools_root = tmp_path / "external_tools"
external_tools_root.mkdir(parents=True)
duplicate_tool_source = "\n".join(
[
"from typing import Any, Dict",
"from reachy_mini_conversation_rmscript.tools.core_tools import Tool, ToolDependencies",
"",
"class DupTool(Tool):",
' name = "dup_tool"',
' description = "Duplicate tool name"',
' parameters_schema = {"type": "object", "properties": {}, "required": []}',
"",
" async def __call__(self, deps: ToolDependencies, **kwargs: Any) -> Dict[str, Any]:",
' return {"status": "ok"}',
"",
]
)
(external_tools_root / "ext_dup_a.py").write_text(duplicate_tool_source, encoding="utf-8")
(external_tools_root / "ext_dup_b.py").write_text(duplicate_tool_source, encoding="utf-8")
monkeypatch.setattr(config_mod.config, "REACHY_MINI_CUSTOM_PROFILE", "default")
monkeypatch.setattr(config_mod.config, "PROFILES_DIRECTORY", config_mod.DEFAULT_PROFILES_DIRECTORY)
monkeypatch.setattr(config_mod.config, "TOOLS_DIRECTORY", external_tools_root)
monkeypatch.setattr(config_mod.config, "AUTOLOAD_EXTERNAL_TOOLS", True)
with pytest.raises(RuntimeError, match="Duplicate Tool.name values detected"):
_reload_core_tools()
def test_builtin_profile_can_load_profile_local_tools(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Built-in profile-local tools should load from the packaged profiles root."""
monkeypatch.setattr(config_mod.config, "REACHY_MINI_CUSTOM_PROFILE", "example")
monkeypatch.setattr(config_mod.config, "PROFILES_DIRECTORY", config_mod.DEFAULT_PROFILES_DIRECTORY)
monkeypatch.setattr(config_mod.config, "TOOLS_DIRECTORY", None)
monkeypatch.setattr(config_mod.config, "AUTOLOAD_EXTERNAL_TOOLS", False)
core_tools_mod = _reload_core_tools()
assert "sweep_look" in core_tools_mod.ALL_TOOLS
def test_tool_registry_reloads_when_profile_changes(monkeypatch: pytest.MonkeyPatch) -> None:
"""Runtime profile changes should refresh enabled tools without restarting Python."""
monkeypatch.setattr(config_mod.config, "REACHY_MINI_CUSTOM_PROFILE", "example")
monkeypatch.setattr(config_mod.config, "PROFILES_DIRECTORY", config_mod.DEFAULT_PROFILES_DIRECTORY)
monkeypatch.setattr(config_mod.config, "TOOLS_DIRECTORY", None)
monkeypatch.setattr(config_mod.config, "AUTOLOAD_EXTERNAL_TOOLS", False)
core_tools_mod = _reload_core_tools()
initial_tool_names = {spec["name"] for spec in core_tools_mod.get_tool_specs()}
assert "sweep_look" in initial_tool_names
assert "camera" not in initial_tool_names
monkeypatch.setattr(config_mod.config, "REACHY_MINI_CUSTOM_PROFILE", "default")
reloaded_tool_names = {spec["name"] for spec in core_tools_mod.get_tool_specs()}
assert "camera" in reloaded_tool_names
assert "move_head" in reloaded_tool_names
assert "sweep_look" not in reloaded_tool_names
assert "camera" in core_tools_mod.ALL_TOOLS
assert "sweep_look" not in core_tools_mod.ALL_TOOLS
def test_forced_tool_registry_reload_does_not_duplicate_profile_local_tool(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Reloading a profile-local tool should ignore stale class objects from the previous load."""
monkeypatch.setattr(config_mod.config, "REACHY_MINI_CUSTOM_PROFILE", "example")
monkeypatch.setattr(config_mod.config, "PROFILES_DIRECTORY", config_mod.DEFAULT_PROFILES_DIRECTORY)
monkeypatch.setattr(config_mod.config, "TOOLS_DIRECTORY", None)
monkeypatch.setattr(config_mod.config, "AUTOLOAD_EXTERNAL_TOOLS", False)
core_tools_mod = _reload_core_tools()
core_tools_mod.initialize_tools(force=True)
assert "sweep_look" in core_tools_mod.ALL_TOOLS