Files
autocad-mcp-pro/tests/test_benchmark_v3.py
T
Umut CANandClaude Opus 5 8d9c444b76 bench: matrix v4 adds pid_roundtrip; correctness suite grows to 29 with three P&ID checks
tasks_v4.py extends v3 with one task the reader can fail on its own: the
example spec is drawn through pid_from_spec's code and read back by the
graph builder, which never sees the spec. The runner, the chart and the
doc-number gate now default to v4; --matrix v3 / v2 reproduce the earlier
sets, and tests/test_benchmark_v3.py labels its full-matrix run "v3"
explicitly now that the default has moved.

The plan's adapter check expected the two unnumbered lines to come back
with no line number; draw_line builds one from the default number_format
(bare sequence when size/service/spec/insulation are empty), so the task
asserts the two authored numbers on the two process lines and sequence-
built XDATA numbers on the other two instead.

The published A/B report still covers 26 checks; Task 21 re-runs it
against v1.5.1, which is what clears the remaining doc-number drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-16 13:06:52 +03:00

170 lines
6.3 KiB
Python

"""M9 — the v3 task matrix.
v2 returned 10/10 for this server. That was not a good sign: every task in it
tested something the server was designed around, so the score could only ever
have been full marks. The five tasks added here were chosen because they can
fail, and three of them did fail while being written — a hatch could not be
measured at all, and the boundary tools disagreed with the measurement tool
about the same shape.
Two assertions in this file exist purely to keep the matrix honest rather than
to test behaviour:
* the plan's ``tool_discovery_bilingual`` and ``measurement_massprops`` must
**not** appear, because neither capability shipped; and
* the published competitor reports must not carry the new task ids, because
those servers were never run against them and a zero we invented would read
exactly like a zero we measured.
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from backends.base import UnsupportedCapabilityError
from benchmarks.adapters.autocad_mcp_pro import AutoCADMCPProAdapter
from benchmarks.adapters.base import BenchmarkAdapter
from benchmarks.run_competitors import run_tasks
from benchmarks.tasks_v2 import TASKS_V2
from benchmarks.tasks_v3 import MATRICES, NEW_TASKS_V3, TASKS_V3, task_by_id
PUBLISHED = Path(__file__).resolve().parents[1] / "benchmarks" / "results" / "published"
# ── shape of the matrix ─────────────────────────────────────────────────────
def test_v3_extends_v2_without_disturbing_it():
"""A renumbered or reweighted v2 task would silently invalidate the
published v1.4 reports, which are keyed by task id."""
assert TASKS_V3[: len(TASKS_V2)] == TASKS_V2
assert len(NEW_TASKS_V3) == 5
assert len(TASKS_V3) == 15
def test_every_task_id_is_unique():
ids = [task.task_id for task in TASKS_V3]
assert len(ids) == len(set(ids))
def test_the_two_capabilities_that_did_not_ship_are_not_advertised_as_tasks():
ids = {task.task_id for task in TASKS_V3}
assert "tool_discovery_bilingual" not in ids, (
"the Turkish normalizer was built and removed; this repo is English "
"throughout, so a bilingual task would be a benchmark for nothing"
)
assert "measurement_massprops" not in ids, (
"no centroid or moment-of-inertia tool ships — F2 was cut to entity_measure"
)
assert "tool_discovery" in ids
assert "measure_from_handle" in ids
def test_task_by_id_can_still_address_the_v2_matrix():
assert task_by_id("core_geometry", "v2").category == "creation"
with pytest.raises(KeyError):
task_by_id("hatch_islands", "v2")
def test_matrices_registry_exposes_both_sets():
assert MATRICES["v2"] == TASKS_V2
assert MATRICES["v3"] == TASKS_V3
# ── the reference adapter actually answers them ─────────────────────────────
def test_the_reference_adapter_implements_every_v3_task():
"""An unimplemented task scores `unsupported` and is easy to not notice."""
missing = [
task.task_id
for task in TASKS_V3
if not hasattr(AutoCADMCPProAdapter, f"_task_{task.task_id}")
]
assert missing == []
@pytest.mark.asyncio
@pytest.mark.parametrize("task", NEW_TASKS_V3, ids=lambda t: t.task_id)
async def test_each_new_task_passes_on_the_headless_backend(task, tmp_path):
report = await run_tasks(AutoCADMCPProAdapter(), [task], tmp_path, timeout=120.0)
result = report["results"][0]
assert result["status"] == "pass", result["message"]
assert result["metrics"], "a task that proves nothing reports no evidence"
@pytest.mark.asyncio
async def test_the_full_v3_matrix_runs_green(tmp_path):
report = await run_tasks(AutoCADMCPProAdapter(), TASKS_V3, tmp_path, timeout=180.0, matrix="v3")
assert report["summary"]["attempted"] == 15
assert report["summary"]["passed"] == 15, [
(item["task_id"], item["status"], item["message"]) for item in report["results"]
]
assert report["matrix"] == "v3"
# ── a capability refusal is not a failure ───────────────────────────────────
class RefusingAdapter(BenchmarkAdapter):
name = "refusing"
async def setup(self, artifact_dir):
return None
async def run_task(self, task):
raise UnsupportedCapabilityError("chspace", "no change-space member on this engine")
async def cleanup(self):
return None
@pytest.mark.asyncio
async def test_a_capability_refusal_is_reported_unsupported_not_failed(tmp_path):
"""`fail` and `unsupported` are different claims about a competitor.
"Your server got this wrong" and "your engine cannot reach this" are not
the same finding, and the runner used to collapse the second into the
first because a refusal is still an exception.
"""
report = await run_tasks(RefusingAdapter(), [NEW_TASKS_V3[0]], tmp_path)
result = report["results"][0]
assert result["status"] == "unsupported"
assert "chspace" in result["message"]
assert report["summary"]["unsupported"] == 1
assert report["summary"]["score"] == 0.0
# ── nothing was invented for the competitors ────────────────────────────────
@pytest.mark.parametrize(
"filename", ["beiming183-autocad-mcp.json", "puran-water-autocad-mcp.json"]
)
def test_competitor_reports_carry_no_result_for_the_new_tasks(filename):
report = json.loads((PUBLISHED / filename).read_text(encoding="utf-8"))
scored = {item["task_id"] for item in report["results"]}
new_ids = {task.task_id for task in NEW_TASKS_V3}
assert scored & new_ids == set(), (
"these servers were pinned and run against the v2 matrix at v1.4; a "
"result here would be fabricated"
)
assert report.get("matrix", "v2") == "v2"
def test_the_published_reference_report_covers_the_whole_v3_matrix():
report = json.loads((PUBLISHED / "autocad-mcp-pro.json").read_text(encoding="utf-8"))
scored = {item["task_id"] for item in report["results"]}
assert scored == {task.task_id for task in TASKS_V3}
assert report["matrix"] == "v3"
assert report["summary"]["score"] == 100.0