Files
tracer-cloud--opensre/tests/tools/test_registry.py
T
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

1007 lines
39 KiB
Python

from __future__ import annotations
import inspect
import logging
from collections.abc import Generator
from types import ModuleType
from typing import Any
import pytest
from core.domain.types.retrieval import RetrievalControls
from core.tool_framework.base import BaseTool
from core.tool_framework.registered_tool import REGISTERED_TOOL_ATTR, RegisteredTool
from core.tool_framework.tool_decorator import tool
from tools import registry as registry_module
from tools import registry_discovery
from tools.investigation_registry.actions import get_available_actions
_V2_TOOL_CONTRACT_NAMES = frozenset(
{
"query_grafana_metrics",
"describe_rds_instance",
"get_postgresql_slow_queries",
"query_datadog_metrics",
"list_eks_pods",
}
)
@pytest.fixture(autouse=True)
def _reset_registry_cache() -> Generator[None]:
registry_module.clear_tool_registry_cache()
yield
registry_module.clear_tool_registry_cache()
def test_tool_decorator_registers_function_tool_with_inferred_schema() -> None:
module: Any = ModuleType("tools.fake_function_tool")
@tool(
name="lookup_incident",
description="Lookup incident metadata.",
display_name="Incident metadata",
source="knowledge",
surfaces=("investigation", "chat"),
)
def lookup_incident(incident_id: str, limit: int = 10) -> dict[str, object]:
return {"incident_id": incident_id, "limit": limit}
lookup_incident.__module__ = module.__name__
module.lookup_incident = lookup_incident
tools = registry_discovery.collect_registered_tools_from_module(module)
assert [tool_def.name for tool_def in tools] == ["lookup_incident"]
registered = tools[0]
assert registered.input_schema["properties"]["incident_id"]["type"] == "string"
assert registered.input_schema["properties"]["limit"]["type"] == "integer"
assert registered.display_name == "Incident metadata"
assert registered.input_schema["required"] == ["incident_id"]
assert registered.surfaces == ("investigation", "chat")
def test_tool_decorator_supports_minimal_single_file_function_tool() -> None:
module: Any = ModuleType("tools.single_file_status_tool")
@tool(source="knowledge")
def check_status(run_id: str, include_history: bool = False) -> dict[str, object]:
"""Check status for a run."""
return {"run_id": run_id, "include_history": include_history}
check_status.__module__ = module.__name__
module.check_status = check_status
tools = registry_discovery.collect_registered_tools_from_module(module)
assert [tool_def.name for tool_def in tools] == ["check_status"]
registered = tools[0]
assert registered.description == "Check status for a run."
assert registered.source == "knowledge"
assert registered.input_schema["properties"]["run_id"]["type"] == "string"
assert registered.input_schema["properties"]["include_history"]["type"] == "boolean"
assert registered.input_schema["required"] == ["run_id"]
assert registered.surfaces == ("investigation",)
assert registered.run(run_id="r-1", include_history=True) == {
"run_id": "r-1",
"include_history": True,
}
def test_function_and_class_tools_share_the_same_runtime_contract() -> None:
def _available(sources: dict[str, dict[str, str]]) -> bool:
return bool(sources.get("knowledge"))
def _extract(sources: dict[str, dict[str, str]]) -> dict[str, str]:
return {"incident_id": sources["knowledge"]["incident_id"]}
@tool(
name="lookup_incident_function",
description="Lookup incident metadata.",
source="knowledge",
input_schema={
"type": "object",
"properties": {
"incident_id": {
"type": "string",
"description": "Incident identifier",
},
},
"required": ["incident_id"],
},
surfaces=("investigation", "chat"),
is_available=_available,
extract_params=_extract,
outputs={"incident_id": "Incident identifier"},
)
def lookup_incident_function(incident_id: str) -> dict[str, str]:
return {"incident_id": incident_id}
class LookupIncidentClassTool(BaseTool):
name = "lookup_incident_class"
description = "Lookup incident metadata."
source = "knowledge"
surfaces = ("investigation", "chat")
input_schema = {
"type": "object",
"properties": {
"incident_id": {
"type": "string",
"description": "Incident identifier",
},
},
"required": ["incident_id"],
}
outputs = {"incident_id": "Incident identifier"}
def is_available(self, sources: dict[str, dict[str, str]]) -> bool:
return _available(sources)
def extract_params(self, sources: dict[str, dict[str, str]]) -> dict[str, str]:
return _extract(sources)
def run(self, incident_id: str) -> dict[str, str]:
return {"incident_id": incident_id}
function_tool = getattr(lookup_incident_function, REGISTERED_TOOL_ATTR)
assert isinstance(function_tool, RegisteredTool)
class_tool = RegisteredTool.from_base_tool(LookupIncidentClassTool())
sources = {"knowledge": {"incident_id": "inc-123"}}
assert function_tool.inputs == class_tool.inputs
assert function_tool.extract_params(sources) == class_tool.extract_params(sources)
assert function_tool.is_available(sources) is class_tool.is_available(sources)
assert function_tool.run(**function_tool.extract_params(sources)) == class_tool.run(
**class_tool.extract_params(sources)
)
assert function_tool.surfaces == class_tool.surfaces
def test_tool_decorator_allows_retrieval_controls_override_for_base_tool() -> None:
class LookupIncidentClassTool(BaseTool):
name = "lookup_incident_class"
description = "Lookup incident metadata."
source = "knowledge"
surfaces = ("investigation", "chat")
retrieval_controls = RetrievalControls(limit=True)
input_schema = {
"type": "object",
"properties": {
"incident_id": {
"type": "string",
"description": "Incident identifier",
},
},
"required": ["incident_id"],
}
def run(self, incident_id: str) -> dict[str, str]:
return {"incident_id": incident_id}
class_tool = tool(
LookupIncidentClassTool(),
retrieval_controls=RetrievalControls(time_bounds=True, filters=True),
)
registered = getattr(class_tool, REGISTERED_TOOL_ATTR)
assert isinstance(registered, RegisteredTool)
assert registered.retrieval_controls.time_bounds
assert registered.retrieval_controls.filters
assert not registered.retrieval_controls.limit
def test_tool_decorator_preserves_tags_for_base_tool_instances() -> None:
class LookupIncidentClassTool(BaseTool):
name = "lookup_incident_class"
description = "Lookup incident metadata."
source = "knowledge"
input_schema = {
"type": "object",
"properties": {
"incident_id": {
"type": "string",
"description": "Incident identifier",
},
},
"required": ["incident_id"],
}
def run(self, incident_id: str) -> dict[str, str]:
return {"incident_id": incident_id}
decorated = tool(
LookupIncidentClassTool(),
tags=("safe", "fast"),
)
registered = getattr(decorated, REGISTERED_TOOL_ATTR)
assert isinstance(registered, RegisteredTool)
assert registered.tags == ("safe", "fast")
def test_auto_discovery_populates_investigation_and_chat_surfaces(
monkeypatch: pytest.MonkeyPatch,
) -> None:
module: Any = ModuleType("tools.fake_discovered_tool")
@tool(
name="get_incident_metadata",
description="Return normalized incident metadata.",
source="knowledge",
surfaces=("investigation", "chat"),
)
def get_incident_metadata(incident_id: str) -> dict[str, str]:
return {"incident_id": incident_id}
get_incident_metadata.__module__ = module.__name__
module.get_incident_metadata = get_incident_metadata
monkeypatch.setattr(
registry_discovery, "_iter_tool_module_names", lambda _pkg: ["fake_discovered_tool"]
)
monkeypatch.setattr(registry_module, "_external_tool_packages", [])
monkeypatch.setattr(registry_module, "INTEGRATION_TOOL_PACKAGES", ())
monkeypatch.setattr(registry_discovery, "_import_tool_module", lambda _pkg, _name: module)
# Surface-scoped loads read the on-disk descriptor index; assert surface
# assignment on the mocked full snapshot instead.
snapshot = registry_module.get_registered_tools()
assert [t.name for t in snapshot if "investigation" in (t.surfaces or ())] == [
"get_incident_metadata"
]
assert [t.name for t in snapshot if "chat" in (t.surfaces or ())] == ["get_incident_metadata"]
assert registry_module.get_registered_tool_map()["get_incident_metadata"].run("inc-1") == {
"incident_id": "inc-1"
}
def test_action_surface_is_filtered_separately(monkeypatch: pytest.MonkeyPatch) -> None:
module: Any = ModuleType("tools.fake_action_tool")
@tool(
name="perform_action",
description="Perform a test action.",
source="knowledge",
surfaces=("action",),
)
def perform_action(value: str) -> dict[str, str]:
return {"value": value}
perform_action.__module__ = module.__name__
module.perform_action = perform_action
monkeypatch.setattr(
registry_discovery, "_iter_tool_module_names", lambda _pkg: ["fake_action_tool"]
)
monkeypatch.setattr(registry_module, "_external_tool_packages", [])
monkeypatch.setattr(registry_module, "INTEGRATION_TOOL_PACKAGES", ())
monkeypatch.setattr(registry_discovery, "_import_tool_module", lambda _pkg, _name: module)
# Surface-scoped loads read the on-disk descriptor index; assert surface
# filtering on the mocked full snapshot instead.
snapshot = registry_module.get_registered_tools()
assert [t.name for t in snapshot if "action" in (t.surfaces or ())] == ["perform_action"]
assert [t.name for t in snapshot if "investigation" in (t.surfaces or ())] == []
assert [t.name for t in snapshot if "chat" in (t.surfaces or ())] == []
def test_github_workflow_skill_guidance_is_attached_to_chat_and_investigation_tools() -> None:
marker = "Use this workflow when the user asks about GitHub engineering status"
shared_guided_tool_names = {
"list_github_work_items",
"summarize_github_pr_status",
"list_github_security_alerts",
"generate_work_status_report",
"summarize_community_followups",
}
for surface in ("chat", "investigation"):
tools_by_name = {
tool_def.name: tool_def for tool_def in registry_module.get_registered_tools(surface)
}
for tool_name in shared_guided_tool_names:
tool_def = tools_by_name[tool_name]
assert marker in tool_def.description
assert "Workflow guidance:" in tool_def.description
assert marker in tool_def.skill_guidance
chat_tools_by_name = {
tool_def.name: tool_def for tool_def in registry_module.get_registered_tools("chat")
}
for tool_name in {
"propose_github_issue_mutation_from_slack",
"execute_github_issue_mutation",
}:
tool_def = chat_tools_by_name[tool_name]
assert marker in tool_def.description
assert "Workflow guidance:" in tool_def.description
assert marker in tool_def.skill_guidance
def test_github_workflow_skill_guidance_does_not_attach_to_unrelated_github_tools() -> None:
tools_by_name = {tool_def.name: tool_def for tool_def in registry_module.get_registered_tools()}
tool_def = tools_by_name["get_github_file_contents"]
assert "Workflow guidance:" not in tool_def.description
assert tool_def.skill_guidance == ""
def test_python_execution_skill_guidance_does_not_attach_to_unrelated_tools() -> None:
tools_by_name = {tool_def.name: tool_def for tool_def in registry_module.get_registered_tools()}
tool_def = tools_by_name["get_github_file_contents"]
assert "github-star-velocity" not in tool_def.skill_guidance
def test_github_issue_mutation_execution_remains_chat_only() -> None:
chat_tools = {
tool_def.name: tool_def for tool_def in registry_module.get_registered_tools("chat")
}
investigation_tools = {
tool_def.name for tool_def in registry_module.get_registered_tools("investigation")
}
tool_def = chat_tools["execute_github_issue_mutation"]
assert "execute_github_issue_mutation" not in investigation_tools
assert tool_def.surfaces == ("chat",)
assert tool_def.requires_approval is False
assert "never an investigation action" in tool_def.description
def test_manifest_discovery_imports_nested_tool_modules(
monkeypatch: pytest.MonkeyPatch,
) -> None:
provider_pkg: Any = ModuleType("tools.fake_provider")
provider_pkg.__path__ = [] # type: ignore[attr-defined]
provider_pkg.TOOL_MODULES = ("nested.incident_tools",)
nested_module: Any = ModuleType("tools.fake_provider.nested.incident_tools")
@tool(
name="lookup_nested_incident",
description="Lookup nested incident metadata.",
source="knowledge",
)
def lookup_nested_incident(incident_id: str) -> dict[str, str]:
return {"incident_id": incident_id}
lookup_nested_incident.__module__ = nested_module.__name__
nested_module.lookup_nested_incident = lookup_nested_incident
monkeypatch.setattr(registry_module, "_external_tool_packages", [])
monkeypatch.setattr(registry_module, "INTEGRATION_TOOL_PACKAGES", ())
monkeypatch.setattr(
registry_discovery,
"_iter_tool_module_names",
lambda package: ["fake_provider"] if package is registry_module.tools_package else [],
)
def mock_import(package: ModuleType, name: str) -> ModuleType:
if package is registry_module.tools_package and name == "fake_provider":
return provider_pkg
if package is provider_pkg and name == "nested.incident_tools":
return nested_module
raise AssertionError(f"unexpected import: {package.__name__}.{name}")
monkeypatch.setattr(registry_discovery, "_import_tool_module", mock_import)
tools = registry_module.get_registered_tools()
assert [tool_def.name for tool_def in tools] == ["lookup_nested_incident"]
assert registry_module.get_registered_tool_map()["lookup_nested_incident"].run(
incident_id="inc-1"
) == {"incident_id": "inc-1"}
def test_manifest_discovery_logs_nested_import_failure_with_full_module_path(
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
provider_pkg: Any = ModuleType("tools.fake_provider")
provider_pkg.__path__ = [] # type: ignore[attr-defined]
provider_pkg.TOOL_MODULES = ("nested.broken_tools", "nested.valid_tools")
valid_module: Any = ModuleType("tools.fake_provider.nested.valid_tools")
@tool(
name="valid_nested_tool",
description="A valid nested tool.",
source="knowledge",
)
def valid_nested_tool() -> dict[str, str]:
return {"status": "ok"}
valid_nested_tool.__module__ = valid_module.__name__
valid_module.valid_nested_tool = valid_nested_tool
monkeypatch.setattr(registry_module, "_external_tool_packages", [])
monkeypatch.setattr(registry_module, "INTEGRATION_TOOL_PACKAGES", ())
monkeypatch.setattr(
registry_discovery,
"_iter_tool_module_names",
lambda package: ["fake_provider"] if package is registry_module.tools_package else [],
)
def mock_import(package: ModuleType, name: str) -> ModuleType:
if package is registry_module.tools_package and name == "fake_provider":
return provider_pkg
if package is provider_pkg and name == "nested.broken_tools":
raise RuntimeError("nested initialization failed")
if package is provider_pkg and name == "nested.valid_tools":
return valid_module
raise AssertionError(f"unexpected import: {package.__name__}.{name}")
monkeypatch.setattr(registry_discovery, "_import_tool_module", mock_import)
with caplog.at_level(logging.WARNING, logger="tools.registry"):
tools = registry_module.get_registered_tools()
assert [tool_def.name for tool_def in tools] == ["valid_nested_tool"]
assert any(
"tools.fake_provider.nested.broken_tools" in record.message
and "nested initialization failed" in record.message
for record in caplog.records
)
def test_manifest_discovery_preserves_duplicate_name_first_wins(
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
provider_pkg: Any = ModuleType("tools.fake_provider")
provider_pkg.__path__ = [] # type: ignore[attr-defined]
provider_pkg.TOOL_MODULES = ("nested.shadow_tools",)
nested_module: Any = ModuleType("tools.fake_provider.nested.shadow_tools")
@tool(
name="shared_manifest_tool",
description="Top-level package tool.",
source="knowledge",
)
def top_level_tool() -> dict[str, str]:
return {"source": "top-level"}
@tool(
name="shared_manifest_tool",
description="Nested package tool.",
source="knowledge",
)
def nested_tool() -> dict[str, str]:
return {"source": "nested"}
top_level_tool.__module__ = provider_pkg.__name__
nested_tool.__module__ = nested_module.__name__
provider_pkg.top_level_tool = top_level_tool
nested_module.nested_tool = nested_tool
monkeypatch.setattr(registry_module, "_external_tool_packages", [])
monkeypatch.setattr(registry_module, "INTEGRATION_TOOL_PACKAGES", ())
monkeypatch.setattr(
registry_discovery,
"_iter_tool_module_names",
lambda package: ["fake_provider"] if package is registry_module.tools_package else [],
)
monkeypatch.setattr(
registry_discovery,
"_import_tool_module",
lambda package, name: (
provider_pkg
if package is registry_module.tools_package and name == "fake_provider"
else nested_module
),
)
with caplog.at_level(logging.WARNING, logger="tools.registry"):
tool_map = registry_module.get_registered_tool_map()
assert list(tool_map) == ["shared_manifest_tool"]
assert tool_map["shared_manifest_tool"].run() == {"source": "top-level"}
assert any(
"Duplicate tool name 'shared_manifest_tool' across modules" in record.message
for record in caplog.records
)
def test_top_level_discovery_unchanged_without_manifest(
monkeypatch: pytest.MonkeyPatch,
) -> None:
top_level_module: Any = ModuleType("tools.fake_top_level_tool")
@tool(
name="top_level_status",
description="Return top-level status.",
source="knowledge",
)
def top_level_status() -> dict[str, str]:
return {"status": "ok"}
top_level_status.__module__ = top_level_module.__name__
top_level_module.top_level_status = top_level_status
import_calls: list[tuple[str, str]] = []
monkeypatch.setattr(registry_module, "_external_tool_packages", [])
monkeypatch.setattr(registry_module, "INTEGRATION_TOOL_PACKAGES", ())
monkeypatch.setattr(
registry_discovery,
"_iter_tool_module_names",
lambda package: ["fake_top_level_tool"] if package is registry_module.tools_package else [],
)
def mock_import(package: ModuleType, name: str) -> ModuleType:
import_calls.append((package.__name__, name))
return top_level_module
monkeypatch.setattr(registry_discovery, "_import_tool_module", mock_import)
tools = registry_module.get_registered_tools()
assert [tool_def.name for tool_def in tools] == ["top_level_status"]
assert import_calls == [("tools", "fake_top_level_tool")]
def test_resolve_tool_display_name_prefers_registered_metadata(
monkeypatch: pytest.MonkeyPatch,
) -> None:
module: Any = ModuleType("tools.fake_display_name_tool")
@tool(
name="get_incident_metadata",
display_name="Incident metadata",
description="Return normalized incident metadata.",
source="knowledge",
)
def get_incident_metadata(incident_id: str) -> dict[str, str]:
return {"incident_id": incident_id}
get_incident_metadata.__module__ = module.__name__
module.get_incident_metadata = get_incident_metadata
monkeypatch.setattr(
registry_discovery, "_iter_tool_module_names", lambda _pkg: ["fake_display_name_tool"]
)
monkeypatch.setattr(registry_discovery, "_import_tool_module", lambda _pkg, _name: module)
assert registry_module.resolve_tool_display_name("get_incident_metadata") == "Incident metadata"
def test_resolve_tool_display_name_falls_back_for_unknown_tools() -> None:
assert (
registry_module.resolve_tool_display_name("nonexistent_tool_xyz_sentinel")
== "nonexistent tool xyz sentinel"
)
def test_real_registry_discovers_migrated_sre_guidance_tool() -> None:
action_names = {tool_def.name for tool_def in get_available_actions()}
assert "get_sre_guidance" in action_names
def test_real_registry_discovers_honeycomb_and_coralogix_tools() -> None:
action_names = {tool_def.name for tool_def in get_available_actions()}
assert {"query_honeycomb_traces", "query_coralogix_logs"} <= action_names
def test_real_registry_preserves_existing_chat_tool_surface() -> None:
chat_names = {tool_def.name for tool_def in registry_module.get_registered_tools("chat")}
assert {"fetch_failed_run", "get_tracer_run", "search_github_code"} <= chat_names
def test_real_registry_does_not_emit_duplicate_tool_warnings(
caplog: pytest.LogCaptureFixture,
) -> None:
saved_external_packages = list(registry_module._external_tool_packages)
try:
registry_module._external_tool_packages.clear()
registry_module.clear_tool_registry_cache()
with caplog.at_level(logging.WARNING, logger="tools.registry"):
tool_names = [tool_def.name for tool_def in registry_module.get_registered_tools()]
assert len(tool_names) == len(set(tool_names))
assert {"get_supabase_service_health", "get_supabase_storage_buckets"} <= set(tool_names)
assert not any("Duplicate tool name" in record.message for record in caplog.records)
finally:
registry_module._external_tool_packages[:] = saved_external_packages
registry_module.clear_tool_registry_cache()
def test_registry_regression_duplicate_tool_names_across_modules(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
"""Test that when two modules export the same tool name, only the first is kept."""
module1: Any = ModuleType("tools.first_module")
module2: Any = ModuleType("tools.second_module")
first_tool = tool(
name="shared_tool_name",
description="Tool in first module.",
source="knowledge",
)(lambda: {"module": "first"})
second_tool = tool(
name="shared_tool_name",
description="Tool in second module.",
source="knowledge",
)(lambda: {"module": "second"})
first_tool.__module__ = module1.__name__
second_tool.__module__ = module2.__name__
module1.shared_tool_first = first_tool
module2.shared_tool_second = second_tool
monkeypatch.setattr(
registry_discovery,
"_iter_tool_module_names",
lambda _pkg: ["first_module", "second_module"],
)
monkeypatch.setattr(
registry_discovery,
"_import_tool_module",
lambda _pkg, name: module1 if name == "first_module" else module2,
)
with caplog.at_level(logging.WARNING, logger="tools.registry"):
tools = registry_module.get_registered_tools()
tool_names = [t.name for t in tools]
assert tool_names.count("shared_tool_name") == 1
registered_tool = registry_module.get_registered_tool_map()["shared_tool_name"]
assert registered_tool.run() == {"module": "first"}
assert any(
"Duplicate tool name 'shared_tool_name' across modules" in record.message
for record in caplog.records
if record.levelname == "WARNING"
)
def test_registry_regression_import_failures(
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
) -> None:
"""Test that registry gracefully skips modules with import failures."""
module: Any = ModuleType("tools.valid_tool")
@tool(
name="valid_tool",
description="A valid tool.",
source="knowledge",
)
def valid_tool() -> dict[str, str]:
return {"status": "ok"}
valid_tool.__module__ = module.__name__
module.valid_tool = valid_tool
def mock_import(_pkg: ModuleType, name: str) -> ModuleType:
if name == "broken_module":
raise RuntimeError("Module initialization failed")
return module
monkeypatch.setattr(
registry_discovery,
"_iter_tool_module_names",
lambda _pkg: ["broken_module", "valid_tool"],
)
monkeypatch.setattr(
registry_discovery,
"_import_tool_module",
mock_import,
)
with caplog.at_level(logging.WARNING, logger="tools.registry"):
tools = registry_module.get_registered_tools()
tool_names = [t.name for t in tools]
assert "valid_tool" in tool_names
assert registry_module.get_registered_tool_map()["valid_tool"].run() == {"status": "ok"}
# Log message now includes the package prefix (``tools.``) so the
# operator can tell WHICH discovery path failed when multiple packages
# are registered.
assert any(
"broken_module" in record.message and record.levelname == "WARNING"
for record in caplog.records
)
def _v2_tools() -> list[RegisteredTool]:
return [
tool
for tool in registry_module.get_registered_tools()
if tool.name in _V2_TOOL_CONTRACT_NAMES
]
def test_v2_registry_tool_contracts_exist() -> None:
discovered = {tool.name for tool in _v2_tools()}
assert discovered == _V2_TOOL_CONTRACT_NAMES
def test_v2_registry_schemas_are_closed_objects() -> None:
for tool_def in _v2_tools():
schema = tool_def.public_input_schema
assert schema.get("type") == "object"
assert schema.get("additionalProperties") is False
def test_v2_registry_property_schemas_are_typed_and_described() -> None:
for tool_def in _v2_tools():
schema = tool_def.public_input_schema
properties = schema.get("properties", {})
assert isinstance(properties, dict)
for prop_name, prop_schema in properties.items():
assert isinstance(prop_schema, dict), (
f"{tool_def.name}.{prop_name} must have an object property schema."
)
has_type = isinstance(prop_schema.get("type"), str) or isinstance(
prop_schema.get("oneOf"), list
)
has_type = has_type or isinstance(prop_schema.get("anyOf"), list)
assert has_type, f"{tool_def.name}.{prop_name} must declare type or oneOf."
assert str(prop_schema.get("description", "")).strip(), (
f"{tool_def.name}.{prop_name} must include description."
)
def test_v2_registry_required_fields_are_declared() -> None:
for tool_def in _v2_tools():
schema = tool_def.public_input_schema
properties = schema.get("properties", {})
required = schema.get("required", [])
assert isinstance(properties, dict)
assert isinstance(required, list)
for required_field in required:
assert required_field in properties, (
f"{tool_def.name} required field {required_field!r} missing from schema properties."
)
def test_v2_registry_public_schema_matches_run_signature() -> None:
for tool_def in _v2_tools():
schema = tool_def.public_input_schema
schema_props = set(schema.get("properties", {}).keys())
run_sig = inspect.signature(tool_def.run)
public_params = {
param.name
for param in run_sig.parameters.values()
if param.kind
in (
inspect.Parameter.POSITIONAL_OR_KEYWORD,
inspect.Parameter.KEYWORD_ONLY,
)
and not param.name.startswith("_")
and param.name not in set(tool_def.injected_params)
}
assert schema_props == public_params, (
f"{tool_def.name} schema properties do not match run signature public params."
)
def test_v2_registry_injected_params_are_hidden_from_public_schema() -> None:
for tool_def in _v2_tools():
schema = tool_def.public_input_schema
props = schema.get("properties", {})
required = schema.get("required", [])
for injected in tool_def.injected_params:
assert injected not in props, f"{tool_def.name} leaked injected param {injected!r}."
assert injected not in required, (
f"{tool_def.name} keeps injected param {injected!r} as required."
)
def test_v2_registry_tools_define_output_schema() -> None:
for tool_def in _v2_tools():
output_schema = tool_def.output_schema
assert isinstance(output_schema, dict), f"{tool_def.name} must define output_schema."
assert output_schema.get("type") == "object"
# --------------------------------------------------------------------------- #
# External tool package registration (extension point for test suites and #
# downstream integrators that ship their own tools outside tools). #
# --------------------------------------------------------------------------- #
def test_register_external_tool_package_adds_tools_to_registry() -> None:
"""A package registered via :func:`register_external_tool_package` has
its top-level submodules walked the same way as ``tools.*``. This
is the mechanism that lets bench-only tool modules live outside ``config/``
while still being discoverable by the agent loop when the bench is
actively running."""
# Importing the cloudopsbench package side-effects registration on its
# own (see tests/benchmarks/cloudopsbench/__init__.py), so by the time
# this test runs the package is already registered.
import tests.benchmarks.cloudopsbench # noqa: F401 — side-effect import
import tests.benchmarks.cloudopsbench.tools as cob_tools_pkg
# The registered list contains the bench tools package.
assert cob_tools_pkg in registry_module._external_tool_packages
# And the registry actually discovers the bench's GetResources tool.
tools = registry_module.get_registered_tools()
tool_names = {t.name for t in tools}
assert "GetResources" in tool_names, (
"bench tools package was registered but its tools didn't surface in the registry"
)
def test_register_external_tool_package_is_thread_safe_under_concurrent_calls() -> None:
"""Greptile P2: the check-then-append used to be a race window — two
threads could both pass the ``not in`` check and both append the same
package, causing every tool in that package to be logged as a duplicate
and silently dropped on subsequent registry walks. The locked version
must register exactly once even under high concurrency."""
import threading
from concurrent.futures import ThreadPoolExecutor
fake_pkg: Any = ModuleType("synthetic.concurrent_registration_pkg")
fake_pkg.__path__ = [] # type: ignore[attr-defined] — empty walk, no tools
barrier = threading.Barrier(parties=8)
saved = list(registry_module._external_tool_packages)
try:
# Make sure the package isn't already registered from a prior test.
if fake_pkg in registry_module._external_tool_packages:
registry_module._external_tool_packages.remove(fake_pkg)
def attempt_register() -> None:
# Synchronize starts so all threads enter the critical section
# within the same scheduling quantum — maximizes race exposure.
barrier.wait()
registry_module.register_external_tool_package(fake_pkg)
with ThreadPoolExecutor(max_workers=8) as pool:
futures = [pool.submit(attempt_register) for _ in range(8)]
for f in futures:
f.result()
# Exactly one registration regardless of how many threads raced.
count = registry_module._external_tool_packages.count(fake_pkg)
assert count == 1, (
f"Expected exactly 1 registration for the same package across 8 "
f"concurrent callers, got {count}. The lock around check-then-"
f"append is missing or broken."
)
finally:
# Restore the registration list to its pre-test state.
registry_module._external_tool_packages[:] = saved
registry_module.clear_tool_registry_cache()
def test_register_external_tool_package_is_idempotent() -> None:
"""Registering the same package twice doesn't add duplicates."""
import tests.benchmarks.cloudopsbench.tools as cob_tools_pkg
initial_count = len(registry_module._external_tool_packages)
registry_module.register_external_tool_package(cob_tools_pkg)
registry_module.register_external_tool_package(cob_tools_pkg)
final_count = len(registry_module._external_tool_packages)
# First call may add (or no-op if already registered); second call must
# never add anything beyond the first.
assert final_count <= initial_count + 1
assert registry_module._external_tool_packages.count(cob_tools_pkg) == 1
def test_production_registry_does_not_include_bench_tools_without_import() -> None:
"""Sanity-check separation: with the bench package *not* imported,
only ``tools.*`` modules contribute to the registry. The bench
tools must be invisible.
We can't fully un-import the bench in a single test process (other
tests in this run may have imported it), but we can verify the
registry walks only the canonical package when external registrations
are cleared."""
saved = list(registry_module._external_tool_packages)
try:
registry_module._external_tool_packages.clear()
registry_module.clear_tool_registry_cache()
tools = registry_module.get_registered_tools()
tool_names = {t.name for t in tools}
# GetResources, GetAlerts, etc. are bench-only; not in tools.
assert "GetResources" not in tool_names
assert "GetAlerts" not in tool_names
finally:
# Restore so subsequent tests in this module see the bench package.
registry_module._external_tool_packages.extend(saved)
registry_module.clear_tool_registry_cache()
def test_registry_clears_cache_on_external_registration_so_new_tools_appear(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The lru_cache on _load_registry_snapshot would freeze the tool list
on the first lookup. register_external_tool_package MUST clear the
cache or registrations after that first lookup would be invisible —
a silent bug class."""
# Prime the cache as the production lifecycle would.
initial_tools = registry_module.get_registered_tools()
initial_names = {t.name for t in initial_tools}
# Register a synthetic external package after the cache is warm.
fake_pkg: Any = ModuleType("synthetic.tool_pkg_for_cache_test")
fake_pkg.__path__ = [] # type: ignore[attr-defined] — empty walk, no tools
saved = list(registry_module._external_tool_packages)
try:
registry_module.register_external_tool_package(fake_pkg)
# Cache must have been cleared; next lookup re-runs discovery.
# No tools to add from fake_pkg, but the call shouldn't crash and
# the result shape must be equivalent to before.
post_register_tools = registry_module.get_registered_tools()
post_register_names = {t.name for t in post_register_tools}
# The canonical tools are still there.
assert initial_names == post_register_names
finally:
# Drop the synthetic package from the registration list to avoid
# leaking into other tests.
registry_module._external_tool_packages[:] = saved
registry_module.clear_tool_registry_cache()
def test_registry_canonical_tool_wins_when_external_package_redefines_name(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""First registration wins for a given tool name. The canonical
``tools.*`` package is walked first, so a downstream integrator
can't accidentally shadow a production tool with their own version of
the same name — only a clear log warning, original tool preserved."""
canonical_tool_name = "ListEKSPodsTool".replace("Tool", "").lower()
# Build a fake external package that 'declares' a tool with a name
# that overlaps with a known canonical tool. We use the same
# discovery path the registry uses, so this is realistic.
pre_existing = {t.name for t in registry_module.get_registered_tools()}
if "list_eks_pods" not in pre_existing:
pytest.skip("Canonical EKS pod tool absent; can't validate shadowing protection.")
fake_module: Any = ModuleType("synthetic.shadowing_module")
@tool(name="list_eks_pods", description="external shadow", source="knowledge")
def shadow_eks_pods() -> dict[str, str]:
return {"shadow": "true"}
shadow_eks_pods.__module__ = fake_module.__name__
fake_module.shadow_eks_pods = shadow_eks_pods
fake_pkg: Any = ModuleType("synthetic.shadow_pkg")
fake_pkg.__path__ = [] # type: ignore[attr-defined]
# Inject the fake by patching the walk + import.
real_iter = registry_discovery._iter_tool_module_names
real_import = registry_discovery._import_tool_module
def fake_iter(package: ModuleType) -> list[str]:
if package is fake_pkg:
return ["shadowing_module"]
return real_iter(package)
def fake_import(package: ModuleType, name: str) -> ModuleType:
if package is fake_pkg and name == "shadowing_module":
return fake_module
return real_import(package, name)
monkeypatch.setattr(registry_discovery, "_iter_tool_module_names", fake_iter)
monkeypatch.setattr(registry_discovery, "_import_tool_module", fake_import)
saved = list(registry_module._external_tool_packages)
try:
registry_module.register_external_tool_package(fake_pkg)
registry_module.clear_tool_registry_cache()
tool_map = registry_module.get_registered_tool_map()
# Canonical tool survives — the shadow was rejected.
canonical = tool_map.get("list_eks_pods")
assert canonical is not None
# Canonical's description is not the shadow's — proves we kept the
# original, not the override.
assert canonical.description != "external shadow", (
f"External package shadowed canonical tool name "
f"{canonical_tool_name!r}. First-registration-wins rule broken."
)
finally:
registry_module._external_tool_packages[:] = saved
registry_module.clear_tool_registry_cache()