The evidence for tracks B and G, in the shape tracks A and E used.
- benchmarks/tasks_v5.py: 18 tasks, DEFAULT_MATRIX = "v5"; v4, v3 and v2 stay
addressable so an earlier report reproduces exactly rather than being only
described. mech_assembly is roadmap criterion 3 - a flange-coupling sheet
built from the part model and the sheet standard, through the code the
tools call (apply_titleblock, draw_part/add_view/dimension_part,
insert_std_part, extract_records -> draw_bom_table, add_balloon), gated on
two numbers that cannot be negotiated: drawing_critique(focus=None) returns
zero issues and the finalize score is at least 90 (it scores 100, 18/18 on
the v5 matrix). The adapter returns the focuses that fired, so a
regression names itself, and a test drops one balloon to prove the gate
fails and names mech_bom_balloon_mismatch.
- Seven correctness checks (32 -> 39). mech_section_hatch_area is the one
that cannot be faked: a sleeve of length 60, outside diameter 40 and bore
20 cuts two faces totalling 1200 mm^2 exactly, read back out of the drawing
through entity_measure. sheet_frame_iso5457_a3 pins the A3 frame (20,10 to
410,287 inside the 420 x 297 trimmed sheet); mech_iso286_on_dimension pins
40 H7 at +0.025/0 through the layout; std_part_iso4014_m12 pins s = 18 and
k = 7.5 by value; mech_thread_unrepresented_is_caught is the seventh: a
quality gate that never fires is not a gate.
- scripts/smoke_mech_com.py executed every new COM path once on AutoCAD 2026
(25.1s): frame, title block, a sectioned shaft with 12 dimensions, an ISO
4014 block with 1 ACADMCP_MECH chunk, a weld symbol, the parts list read off
the drawing, a linked balloon, an xref attached / listed / detached, a
31687-byte .dwg (AC1032) through ActiveX SaveAs, critique 0 issues, exit 0,
every scratch document closed. It retries RPC_E_CALL_REJECTED (measured:
the first call after Documents.Add is refused) and tracks its documents by
the document list, since SaveAs renames them.
- Defects the evidence found, each fixed with a test that fails on the old
code:
* ezdxf dimension_diameter put the text beyond the SECOND chord point;
ActiveX AddDimDiametric puts it beyond the first (measured live: a d50
from (x,170) to (x,220), leader 10, text at (x,160)). Headlessly every
front-view diameter stood on its own chain dimension. ezdxf now follows
ActiveX.
* draw_part's anchor goes on MECH; ActiveX refuses entity.Layer on a
missing layer (measured -0x7ffdfff7 'Key not found'). draw.py ensures
every target layer before the first entity.
* bom.extract_records filtered on INSERT; ActiveX names it BLOCKREFERENCE
(ObjectName AcDbBlockReference) - the live parts list was empty.
* COM _entity_info reported no dimension text_position: ModelSpace.Item
narrows to IAcadEntity, which has no TextPosition (measured
AttributeError); read through _com_unnarrow (TextPosition measured on
AcDbDiametricDimension, WCS). dim_overlap compared bbox centres and a
diameter collided with its own bore.
* pid graph raised KeyError 'start' mid-critique on a LINE whose point read
AutoCAD refused while busy; such a line is left out.
* the validator read "ISO 4014 - M12x60" as fake dimension text.
- The published A/B report is re-run over the grown suite rather than
re-typed (26/39 -> 39/39, 13 fixed, 0 regressed), check_doc_numbers derives
the task count from the v5 matrix, the token table is remeasured with a
TOOL_PACKS=core,mech row (193 tools / 61,673 tokens), and the README's
collected-tests figure is retaken (3990).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
126 lines
4.9 KiB
Python
126 lines
4.9 KiB
Python
# tests/test_benchmark_v4.py
|
|
"""The v4 task matrix: v3 plus what 1.6 added.
|
|
|
|
Track A added ``pid_roundtrip``; track E adds ``page_setup_truth`` — apply an
|
|
ISO A3 landscape page setup, plot through ``batch_plot`` and read the sheet
|
|
size back from the PDF's own ``/MediaBox``. The published competitor reports
|
|
were never asked either question and the chart shows them ``not_run`` — the
|
|
same rule v3 set for its five.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from benchmarks.tasks_v3 import TASKS_V3
|
|
from benchmarks.tasks_v4 import DEFAULT_MATRIX, MATRICES, NEW_TASKS_V4, TASKS_V4, task_by_id
|
|
|
|
|
|
def test_v4_extends_v3_without_touching_it():
|
|
assert TASKS_V4[: len(TASKS_V3)] == TASKS_V3
|
|
assert [t.task_id for t in NEW_TASKS_V4] == ["pid_roundtrip", "page_setup_truth"]
|
|
assert len(TASKS_V4) == 17
|
|
assert MATRICES["v3"] == TASKS_V3 and MATRICES["v4"] == TASKS_V4 and DEFAULT_MATRIX == "v4"
|
|
assert task_by_id("pid_roundtrip").category == "pid"
|
|
assert task_by_id("page_setup_truth").category == "pagesetup"
|
|
|
|
|
|
def test_the_adapter_implements_every_v4_task():
|
|
from benchmarks.adapters.autocad_mcp_pro import AutoCADMCPProAdapter
|
|
|
|
for task in TASKS_V4:
|
|
assert hasattr(AutoCADMCPProAdapter, f"_task_{task.task_id}"), task.task_id
|
|
|
|
|
|
def test_correctness_suite_has_the_pid_checks():
|
|
from benchmarks.correctness_suite import CHECKS
|
|
|
|
for name in ("pid_block_define_attdef_roundtrip", "pid_tag_parse_fic", "pid_graph_edge_count"):
|
|
assert CHECKS[name][1] == "P&ID"
|
|
|
|
|
|
def test_correctness_suite_has_the_settings_checks():
|
|
from benchmarks.correctness_suite import CHECKS
|
|
|
|
for name in (
|
|
"settings_dimstyle_iso25_values",
|
|
"settings_layer_state_roundtrip",
|
|
"settings_pdf_mediabox_a3",
|
|
):
|
|
assert CHECKS[name][1] == "Settings"
|
|
# v4 left the suite at 32; tracks B + G grew it to 39 (tests/test_benchmark_v5.py).
|
|
assert len(CHECKS) == 39
|
|
|
|
|
|
@pytest.mark.asyncio # asyncio_mode is strict; the four tests above are sync
|
|
async def test_page_setup_truth_passes_headlessly(tmp_path):
|
|
"""The benchmark task itself, run once here so a regression in page setup
|
|
or the PDF read-back fails the suite and not only the matrix."""
|
|
from benchmarks.adapters.autocad_mcp_pro import AutoCADMCPProAdapter
|
|
|
|
adapter = AutoCADMCPProAdapter(backend="ezdxf")
|
|
await adapter.setup(tmp_path)
|
|
try:
|
|
await adapter._reset()
|
|
passed, metrics, _artifacts = await adapter._task_page_setup_truth()
|
|
finally:
|
|
await adapter.cleanup()
|
|
assert passed, metrics
|
|
assert abs(metrics["mediabox_mm"][0] - 420.0) < 0.5
|
|
assert abs(metrics["mediabox_mm"][1] - 297.0) < 0.5
|
|
# The stage that makes the task falsifiable: the sheet was ANSI B first.
|
|
assert abs(metrics["mediabox_mm_before"][0] - 432.0) < 0.5
|
|
assert abs(metrics["mediabox_mm_before"][1] - 279.0) < 0.5
|
|
assert metrics["size_mm_changed"] == [[432.0, 279.0], [420.0, 297.0]]
|
|
|
|
|
|
async def _noop_page_setup_apply(self, layout, setup):
|
|
"""A setter that writes nothing and claims success."""
|
|
return {
|
|
"ok": True,
|
|
"layout": layout,
|
|
"applied": dict(setup),
|
|
"changed": {},
|
|
"warnings": [],
|
|
"plot_style_known": True,
|
|
"viewports_kept": True,
|
|
"backend": "ezdxf",
|
|
}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_page_setup_truth_fails_when_the_setter_writes_nothing(tmp_path, monkeypatch):
|
|
"""A fresh Layout1 is already ISO A3 landscape (ezdxf's R2018 default: 420 x 297,
|
|
rotation 0), so a task that only reads A3 back from the PDF cannot tell a
|
|
working setter from a stub. Both gate rows must fail with the stub."""
|
|
pytest.importorskip("matplotlib", reason="rendering needs the [pdf] extra")
|
|
from backends.ezdxf_backend import EzdxfBackend
|
|
from benchmarks import correctness_suite
|
|
from benchmarks.adapters.autocad_mcp_pro import AutoCADMCPProAdapter
|
|
|
|
monkeypatch.setattr(EzdxfBackend, "page_setup_apply", _noop_page_setup_apply)
|
|
|
|
assert await correctness_suite.settings_pdf_mediabox_a3() is False
|
|
|
|
adapter = AutoCADMCPProAdapter(backend="ezdxf")
|
|
await adapter.setup(tmp_path)
|
|
try:
|
|
await adapter._reset()
|
|
passed, metrics, _artifacts = await adapter._task_page_setup_truth()
|
|
finally:
|
|
await adapter.cleanup()
|
|
assert passed is False, metrics
|
|
# The stub's own evidence: the "ANSI B" PDF is still the default A3 sheet.
|
|
assert abs(metrics["mediabox_mm_before"][0] - 420.0) < 0.5
|
|
assert metrics["size_mm_changed"] is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_correctness_check_settings_pdf_mediabox_a3_passes():
|
|
"""The A/B gate row itself, run once here: ANSI B plotted at 432 x 279,
|
|
then ISO A3 at 420 x 297, each read from its own PDF."""
|
|
pytest.importorskip("matplotlib", reason="rendering needs the [pdf] extra")
|
|
from benchmarks import correctness_suite
|
|
|
|
assert await correctness_suite.settings_pdf_mediabox_a3() is True
|