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
170 lines
6.3 KiB
Python
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
|