chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
"""Coverage gate for dispatch.py driven by the golden corpus.
|
||||
|
||||
Uses the stdlib ``trace`` module (no external dependency) to measure which
|
||||
lines of the ``_handler`` closure in :func:`build_tool_handler` are executed
|
||||
by ALL_CASES together.
|
||||
|
||||
Denominator: lines reported by ``code.co_lines()`` for the ``_handler`` code
|
||||
object. This matches exactly the lines that ``trace`` can record — no
|
||||
import-time, factory, or sibling-closure lines are included.
|
||||
|
||||
Target: >=95% line coverage of the _handler body. The remaining allowance is
|
||||
reserved for invariant guards and bytecode layout shifts between CPython
|
||||
releases.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import importlib.util
|
||||
import trace
|
||||
import types
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from test_tools.dispatch_corpus import ALL_CASES
|
||||
|
||||
import opensquilla.tools.dispatch as _dispatch_module
|
||||
from opensquilla.engine.hooks import NoopToolHook
|
||||
from opensquilla.result_budget import ToolRunBudgetExceededError, ToolRunBudgetPolicy
|
||||
from opensquilla.tool_boundary import ToolCall
|
||||
from opensquilla.tools.dispatch import build_tool_handler
|
||||
from opensquilla.tools.registry import ToolRegistry
|
||||
from opensquilla.tools.types import ToolContext, ToolSpec, current_tool_context
|
||||
|
||||
_DISPATCH_SOURCE: Path = Path(_dispatch_module.__file__).resolve()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Executable line discovery via co_lines()
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _all_code_objects(code: types.CodeType): # type: ignore[return]
|
||||
"""Yield all code objects reachable from ``code`` (including nested closures)."""
|
||||
yield code
|
||||
for const in code.co_consts:
|
||||
if isinstance(const, types.CodeType):
|
||||
yield from _all_code_objects(const)
|
||||
|
||||
|
||||
def _handler_executable_lines() -> set[int]:
|
||||
"""Return executable line numbers inside the _handler closure."""
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
_dispatch_module.__name__, str(_DISPATCH_SOURCE)
|
||||
)
|
||||
module_code = spec.loader.get_code(_dispatch_module.__name__) # type: ignore[union-attr]
|
||||
|
||||
executable: set[int] = set()
|
||||
build_tool_handler_code = next(
|
||||
(
|
||||
code
|
||||
for code in _all_code_objects(module_code)
|
||||
if code.co_name == "build_tool_handler"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if build_tool_handler_code is None:
|
||||
return executable
|
||||
for code in _all_code_objects(build_tool_handler_code):
|
||||
if code.co_name != "_handler":
|
||||
continue
|
||||
for _start, _end, lineno in code.co_lines():
|
||||
if lineno is not None:
|
||||
executable.add(lineno)
|
||||
return executable
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Executed line collection via stdlib trace
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class _RaisingToolHook:
|
||||
"""ToolHook that raises in every callback — drives the defensive
|
||||
``except Exception`` branches around hook fan-out so the safety net
|
||||
locks them in place."""
|
||||
|
||||
name = "raising_tool"
|
||||
|
||||
def before_tool(self, call): # type: ignore[no-untyped-def]
|
||||
raise RuntimeError("before_tool boom")
|
||||
|
||||
def after_tool(self, call, outcome): # type: ignore[no-untyped-def]
|
||||
raise RuntimeError("after_tool boom")
|
||||
|
||||
|
||||
def _collect_executed_lines() -> set[int]:
|
||||
"""Return lines of dispatch.py executed across ALL_CASES corpus runs.
|
||||
|
||||
Each case runs three times:
|
||||
|
||||
* ``hooks=None`` — legacy fast path, no hook fan-out.
|
||||
* ``hooks=(NoopToolHook(),)`` — hook seam happy path.
|
||||
* ``hooks=(_RaisingToolHook(),)`` — hook seam exception branches.
|
||||
|
||||
All three paths must stay reachable; a regression that drops any of them
|
||||
will cut coverage well below the floor below.
|
||||
"""
|
||||
|
||||
tracer = trace.Trace(count=True, trace=False)
|
||||
|
||||
async def _run_all() -> None:
|
||||
hook_variants: tuple[tuple, ...] = (
|
||||
(),
|
||||
(NoopToolHook(),),
|
||||
(_RaisingToolHook(),),
|
||||
)
|
||||
for hooks in hook_variants:
|
||||
for case in ALL_CASES:
|
||||
ctx = case.ctx_factory()
|
||||
registry = case.registry_factory()
|
||||
handler = build_tool_handler(
|
||||
registry,
|
||||
ctx,
|
||||
known_skill_names=(
|
||||
set(case.known_skill_names) if case.known_skill_names else None
|
||||
),
|
||||
tool_hooks=hooks or None,
|
||||
)
|
||||
token = current_tool_context.set(None)
|
||||
if case.setup is not None:
|
||||
case.setup()
|
||||
try:
|
||||
await handler(case.tool_call)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
current_tool_context.reset(token)
|
||||
if case.teardown is not None:
|
||||
case.teardown()
|
||||
|
||||
async def _run_coverage_only(
|
||||
*,
|
||||
tool_name: str,
|
||||
handler_exc: BaseException,
|
||||
hooks: tuple,
|
||||
) -> None:
|
||||
registry = ToolRegistry()
|
||||
|
||||
async def _handler() -> str:
|
||||
raise handler_exc
|
||||
|
||||
registry.register(
|
||||
ToolSpec(
|
||||
name=tool_name,
|
||||
description="coverage-only branch driver",
|
||||
parameters={},
|
||||
result_budget_class="external",
|
||||
),
|
||||
_handler,
|
||||
)
|
||||
ctx = ToolContext(
|
||||
tool_run_budget_key=f"coverage-{tool_name}",
|
||||
tool_run_budget_policy=ToolRunBudgetPolicy(
|
||||
max_web_fetch_calls_per_turn=10,
|
||||
max_single_fetch_chars=1_000,
|
||||
max_external_text_chars_per_turn=1_000,
|
||||
),
|
||||
)
|
||||
handler = build_tool_handler(
|
||||
registry,
|
||||
ctx,
|
||||
tool_hooks=hooks or None,
|
||||
)
|
||||
token = current_tool_context.set(None)
|
||||
try:
|
||||
await handler(
|
||||
ToolCall(
|
||||
tool_use_id=f"tc-{tool_name}",
|
||||
tool_name=tool_name,
|
||||
arguments={},
|
||||
)
|
||||
)
|
||||
except BaseException:
|
||||
pass
|
||||
finally:
|
||||
current_tool_context.reset(token)
|
||||
|
||||
for hooks in hook_variants:
|
||||
await _run_coverage_only(
|
||||
tool_name="coverage_cancel",
|
||||
handler_exc=asyncio.CancelledError(),
|
||||
hooks=hooks,
|
||||
)
|
||||
await _run_coverage_only(
|
||||
tool_name="coverage_budget_exhausted",
|
||||
handler_exc=ToolRunBudgetExceededError(
|
||||
"coverage_budget_exhausted",
|
||||
"coverage",
|
||||
),
|
||||
hooks=hooks,
|
||||
)
|
||||
|
||||
tracer.runfunc(asyncio.run, _run_all())
|
||||
|
||||
executed: set[int] = set()
|
||||
for (filename, lineno), count in tracer.counts.items():
|
||||
if Path(filename).resolve() == _DISPATCH_SOURCE and count > 0:
|
||||
executed.add(lineno)
|
||||
return executed
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The gate test
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_dispatch_handler_line_coverage_from_corpus() -> None:
|
||||
"""Assert the corpus achieves >=95% line coverage of the _handler body.
|
||||
|
||||
Denominator: all lines in the _handler closure as reported by co_lines().
|
||||
|
||||
If this test fails, add a new corpus case targeting the uncovered branch,
|
||||
or document the branch here.
|
||||
"""
|
||||
executable = _handler_executable_lines()
|
||||
if not executable:
|
||||
pytest.fail(
|
||||
"Could not determine executable lines for dispatch._handler — "
|
||||
"the _handler closure may have been renamed or moved out of "
|
||||
"build_tool_handler."
|
||||
)
|
||||
assert len(executable) >= 30, (
|
||||
f"Suspiciously few executable lines found in _handler: got {len(executable)}. "
|
||||
"The handler may have moved or the coverage guard may need to follow the "
|
||||
"new dispatch entry point."
|
||||
)
|
||||
|
||||
executed = _collect_executed_lines()
|
||||
covered = executable & executed
|
||||
uncovered = executable - executed
|
||||
|
||||
coverage_pct = len(covered) / len(executable) * 100
|
||||
|
||||
src_lines = _DISPATCH_SOURCE.read_text(encoding="utf-8").splitlines()
|
||||
uncovered_snippets = []
|
||||
for lineno in sorted(uncovered)[:20]:
|
||||
idx = lineno - 1
|
||||
snippet = src_lines[idx].strip() if 0 <= idx < len(src_lines) else "<unknown>"
|
||||
uncovered_snippets.append(f" line {lineno}: {snippet}")
|
||||
|
||||
diagnostic = (
|
||||
f"\nHandler line coverage: {coverage_pct:.1f}%"
|
||||
f" ({len(covered)}/{len(executable)} lines)\n"
|
||||
f"Uncovered lines ({len(uncovered)} total):\n"
|
||||
+ ("\n".join(uncovered_snippets) if uncovered_snippets else " (none)")
|
||||
)
|
||||
|
||||
# 95% floor: the only unreachable lines are the invariant-guard ``raise
|
||||
# RuntimeError`` (PolicyCheck returns a denial without an envelope —
|
||||
# impossible by construction) and bytecode-layout cushion. All hook fan-out
|
||||
# branches, including the defensive ``except Exception`` around each hook
|
||||
# call, are exercised by the no-hook / NoopToolHook / RaisingToolHook
|
||||
# variants above.
|
||||
assert coverage_pct >= 95.0, (
|
||||
f"dispatch.py _handler coverage {coverage_pct:.1f}% < 95% target."
|
||||
f"{diagnostic}"
|
||||
)
|
||||
Reference in New Issue
Block a user