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
107 lines
4.2 KiB
Python
107 lines
4.2 KiB
Python
"""Contract: the static descriptor index matches the imported registry (#3686).
|
|
|
|
The index (AST scan + pinned fallback descriptors) must equal the imported
|
|
registry exactly — same tool names, same surfaces, same source — so surface-
|
|
scoped loads built on it can never diverge from the eager snapshot. If this
|
|
drifts, a tool changed shape: make its metadata literal, or update the pinned
|
|
``_fallback_descriptors`` in ``tools/registry_index.py``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Iterator
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
import tools.registry as registry_module
|
|
from tools.registry_index import build_descriptor_index
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _isolated_registry() -> Iterator[None]:
|
|
# Assert against the on-disk tool set: drop any tools other tests left in the
|
|
# global ``_external_tool_packages`` (the AST index only sees disk).
|
|
saved = list(registry_module._external_tool_packages)
|
|
registry_module._external_tool_packages.clear()
|
|
registry_module.clear_tool_registry_cache()
|
|
yield
|
|
registry_module._external_tool_packages[:] = saved
|
|
registry_module.clear_tool_registry_cache()
|
|
|
|
|
|
def _registered_by_name() -> dict[str, Any]:
|
|
return {tool.name: tool for tool in registry_module.get_registered_tools()}
|
|
|
|
|
|
def test_index_tool_set_matches_registry_exactly() -> None:
|
|
index = set(build_descriptor_index())
|
|
registered = set(_registered_by_name())
|
|
assert index == registered, {
|
|
"missing_from_index": sorted(registered - index),
|
|
"not_registered": sorted(index - registered),
|
|
}
|
|
|
|
|
|
def test_descriptor_surfaces_match_registry() -> None:
|
|
index = build_descriptor_index()
|
|
registered = _registered_by_name()
|
|
mismatched = {
|
|
name: (descriptor.surfaces, tuple(getattr(registered[name], "surfaces", ()) or ()))
|
|
for name, descriptor in index.items()
|
|
if set(descriptor.surfaces) != set(getattr(registered[name], "surfaces", ()) or ())
|
|
}
|
|
assert mismatched == {}
|
|
|
|
|
|
def test_descriptor_source_matches_registry_when_known() -> None:
|
|
index = build_descriptor_index()
|
|
registered = _registered_by_name()
|
|
for name, descriptor in index.items():
|
|
if descriptor.source is None:
|
|
continue
|
|
assert descriptor.source == getattr(registered[name], "source", None), name
|
|
|
|
|
|
def test_descriptor_module_is_dotted_import_path() -> None:
|
|
index = build_descriptor_index()
|
|
assert index["query_datadog_all"].module == "integrations.datadog.tools"
|
|
assert index["shell_run"].module == "tools.interactive_shell.actions.shell"
|
|
|
|
|
|
def test_surface_scoped_load_equals_full_filtered() -> None:
|
|
"""The fast surface path must return exactly the full snapshot filtered by surface.
|
|
|
|
Compares by ``(name, origin_module)`` so a duplicate tool name that resolves to
|
|
a different module in the surface path (alphabetical) than the full path
|
|
(package-declaration order) fails here rather than silently diverging.
|
|
"""
|
|
full = registry_module.get_registered_tools()
|
|
for surface in ("action", "chat", "investigation"):
|
|
scoped = {
|
|
(tool.name, tool.origin_module)
|
|
for tool in registry_module.get_registered_tools(surface)
|
|
}
|
|
expected = {
|
|
(tool.name, tool.origin_module)
|
|
for tool in full
|
|
if surface in (getattr(tool, "surfaces", ()) or ())
|
|
}
|
|
assert scoped == expected, surface
|
|
|
|
|
|
def test_get_tool_descriptors_match_surface_load() -> None:
|
|
assert {d.name for d in registry_module.get_tool_descriptors()} == set(build_descriptor_index())
|
|
for surface in ("action", "chat", "investigation"):
|
|
descriptor_names = {d.name for d in registry_module.get_tool_descriptors(surface)}
|
|
tool_names = {tool.name for tool in registry_module.get_registered_tools(surface)}
|
|
assert descriptor_names == tool_names, surface
|
|
|
|
|
|
def test_load_tool_materializes_the_executor() -> None:
|
|
# @tool-decorated (AST-indexed) and RegisteredTool-constructed (pinned) both load.
|
|
for name in ("query_datadog_all", "shell_run"):
|
|
descriptor = next(d for d in registry_module.get_tool_descriptors() if d.name == name)
|
|
tool = registry_module.load_tool(descriptor)
|
|
assert tool is not None and tool.name == name
|