bench: matrix v4 adds page_setup_truth; correctness suite grows to 32 with three settings checks (A/B vs v1.5.1: 26 -> 32 / 32)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
Umut CAN
2026-09-22 13:07:31 +03:00
co-authored by Claude Opus 5
parent 02dbb133b5
commit c2942d3c94
8 changed files with 230 additions and 40 deletions
+1 -1
View File
@@ -104,7 +104,7 @@ Tools are organized into sections (counts are indicative — `system_about` is a
**Measuring:** never read vertices back and shoelace them — that loses 28.2% of the area on a semicircular edge, silently. `analysis_measure_entity(handle)` reads the real geometry, and its payload states its own accuracy: `exact`, `flatten_tolerance`, `assumed_closed`, `self_intersecting`. A HATCH reports the area it *fills* (outer loops minus islands) plus `hatch_style`; on a curved hatch edge `flatten_tolerance` is not the accuracy knob, because ezdxf hands boundaries over as cubic Beziers whose ~0.028% circle error is already baked in. `boundary_trace` / `boundary_from_entities` report the same number `analysis_measure_entity` gives for the polyline they just drew — if those ever diverge, one of them is approximating and neither says so. **Measuring:** never read vertices back and shoelace them — that loses 28.2% of the area on a semicircular edge, silently. `analysis_measure_entity(handle)` reads the real geometry, and its payload states its own accuracy: `exact`, `flatten_tolerance`, `assumed_closed`, `self_intersecting`. A HATCH reports the area it *fills* (outer loops minus islands) plus `hatch_style`; on a curved hatch edge `flatten_tolerance` is not the accuracy knob, because ezdxf hands boundaries over as cubic Beziers whose ~0.028% circle error is already baked in. `boundary_trace` / `boundary_from_entities` report the same number `analysis_measure_entity` gives for the polyline they just drew — if those ever diverge, one of them is approximating and neither says so.
**Benchmark matrix:** `benchmarks/tasks_v4.py` is the current set (16 tasks; `--matrix v3` / `--matrix v2` reproduce the earlier sets). The published competitor reports are pinned v1.4 runs against v2 and are **never** back-filled for the five v3 tasks or the v4 `pid_roundtrip` — the chart shows them `not run`, not zero. `benchmarks/correctness_suite.py` (29 checks) is the release A/B gate: `python benchmarks/compare_versions.py v1.5.1` (the published report is `benchmarks/results/published/ab-v1.5.1-vs-v1.6.0-dev.json`; `scripts/check_doc_numbers.py` names it in `AB_REPORT`). **Benchmark matrix:** `benchmarks/tasks_v4.py` is the current set (17 tasks; `--matrix v3` / `--matrix v2` reproduce the earlier sets). The published competitor reports are pinned v1.4 runs against v2 and are **never** back-filled for the five v3 tasks or the v4 `pid_roundtrip` / `page_setup_truth` — the chart shows them `not run`, not zero. `benchmarks/correctness_suite.py` (32 checks) is the release A/B gate: `python benchmarks/compare_versions.py v1.5.1` (the published report is `benchmarks/results/published/ab-v1.5.1-vs-v1.6.0-dev.json`; `scripts/check_doc_numbers.py` names it in `AB_REPORT`).
### Adding a New Tool ### Adding a New Tool
+7 -6
View File
@@ -27,7 +27,7 @@ Live through COM on Windows, or headless through ezdxf anywhere — one typed co
</div> </div>
> **v1.5 release snapshot:** 204 tools · 8 resources · 5 prompt templates · 3259 collected tests. > **v1.5 release snapshot:** 204 tools · 8 resources · 5 prompt templates · 3282 collected tests.
> 204 is the **registered** count; a default install advertises 199 over `tools/list`, > 204 is the **registered** count; a default install advertises 199 over `tools/list`,
> because `ENABLE_3D` is unset. `system_about` is the runtime authority. > because `ENABLE_3D` is unset. `system_about` is the runtime authority.
@@ -155,18 +155,18 @@ Self-measurement, produced by scripts in [`benchmarks/`](https://github.com/U-C4
### Correctness — every release re-proves itself ### Correctness — every release re-proves itself
29 deterministic headless checks against the previous tag and the current tree, each in its own subprocess so a hard crash counts as a miss rather than killing the run. 32 deterministic headless checks against the previous tag and the current tree, each in its own subprocess so a hard crash counts as a miss rather than killing the run.
| Version | Checks passing | Pass rate | Fixed | Regressed | | Version | Checks passing | Pass rate | Fixed | Regressed |
|---|---:|---:|---:|---:| |---|---:|---:|---:|---:|
| v1.5.1 *(baseline)* | 26 / 29 | 89.7 % | — | — | | v1.5.1 *(baseline)* | 26 / 32 | 81.2 % | — | — |
| **v1.6.0-dev** *(this branch)* | **29 / 29** | **100 %** | 3 | **0** | | **v1.6.0-dev** *(this branch)* | **32 / 32** | **100 %** | 6 | **0** |
The three fixed rows are the P&ID checks, new capability (`miss → pass`): `block_define` round-trips an ATTDEF, `pid_tag_parse` reads `FIC-101`, and `pid_graph` counts the example sheet's four edges. The 26 checks v1.5.1 was gated on all still pass. Against the older `v1.4.0` baseline the same 26 reported **21 / 26 → 26 / 26, five fixed, zero regressed** — two of those were repaired defects (`fail → pass`), the diameter and radius callouts, which measured the leader as geometry and dimensioned a 40 mm bore as 60 at default settings. The six fixed rows are new capability (`miss → pass`): the three P&ID checks of track A and, from track E, `settings_dimstyle_iso25_values` (the ISO-25 preset lands in the DIMSTYLE table with ISO 129-1's numbers), `settings_layer_state_roundtrip` (save → change → restore puts the layer table back, and the state survives save/reopen because it is an XRECORD in the file) and `settings_pdf_mediabox_a3` (the plotted PDF's own `/MediaBox` reads 420 × 297). The 26 checks v1.5.1 was gated on all still pass. Against the older `v1.4.0` baseline the same 26 reported **21 / 26 → 26 / 26, five fixed, zero regressed** — two of those were repaired defects (`fail → pass`), the diameter and radius callouts, which measured the leader as geometry and dimensioned a 40 mm bore as 60 at default settings.
### The task matrix — six tasks that can fail ### The task matrix — six tasks that can fail
An earlier matrix scored this server 10/10, which carried no information: every task in it exercised something the server was built around. Five were added in 1.5 because they *can* fail, and three did while being written; 1.6 adds a sixth that the P&ID reader can fail on its own. An earlier matrix scored this server 10/10, which carried no information: every task in it exercised something the server was built around. Five were added in 1.5 because they *can* fail, and three did while being written; 1.6 adds two more — one the P&ID reader can fail on its own, one where the PDF file, not the setter, is the witness.
| Task | Verified against | | Task | Verified against |
|---|---| |---|---|
@@ -176,6 +176,7 @@ An earlier matrix scored this server 10/10, which carried no information: every
| `selection_filter` | window 1, crossing 2, bounding box 3, polygon 1 | | `selection_filter` | window 1, crossing 2, bounding box 3, polygon 1 |
| `measure_from_handle` | 139.2699 against the 100.0 a vertex shoelace gives | | `measure_from_handle` | 139.2699 against the 100.0 a vertex shoelace gives |
| `pid_roundtrip` | the example sheet drawn by `pid_from_spec`, read back by `pid_graph`, which never sees the spec: 5 nodes, 4 edges, 0 dangling, `confidence_min` 1.0, `FIC-101` wired to `FCV-101` | | `pid_roundtrip` | the example sheet drawn by `pid_from_spec`, read back by `pid_graph`, which never sees the spec: 5 nodes, 4 edges, 0 dangling, `confidence_min` 1.0, `FIC-101` wired to `FCV-101` |
| `page_setup_truth` | ISO A3 landscape applied to Layout1, plotted through `batch_plot`, and the sheet read back from the PDF's `/MediaBox`: 420 × 297 mm, not the setter's return value |
### Headless performance ### Headless performance
+20 -14
View File
@@ -46,7 +46,7 @@ python -m benchmarks.run_competitors --server autocad-mcp-pro --backend ezdxf --
python -m benchmarks.run_competitors --task table_mleader --task hatch_islands --json python -m benchmarks.run_competitors --task table_mleader --task hatch_islands --json
``` ```
The release-machine ezdxf self-check is **16/16 (100.0)** on the v4 matrix The release-machine ezdxf self-check is **17/17 (100.0)** on the v4 matrix
(`--matrix v3` / `--matrix v2` reproduce the earlier sets). (`--matrix v3` / `--matrix v2` reproduce the earlier sets).
Repository stars and raw tool counts do not contribute to the score. Adapter Repository stars and raw tool counts do not contribute to the score. Adapter
registration lives in `competitors.yaml`. registration lives in `competitors.yaml`.
@@ -87,17 +87,18 @@ zero we invented would be indistinguishable from a zero we measured.
only described, and `--publish` writes the artifact-path-free file that only described, and `--publish` writes the artifact-path-free file that
`results/published/` holds. `results/published/` holds.
### Matrix v4 (v1.6) — the P&ID round trip ### Matrix v4 (v1.6) — the P&ID round trip and the page-setup truth
`tasks_v4.py` keeps the v3 fifteen unchanged and adds one task the reader can `tasks_v4.py` keeps the v3 fifteen unchanged and adds two tasks that can each
fail on its own: fail on their own:
| Task | Category | Verified against | | Task | Category | Verified against |
|---|---|---| |---|---|---|
| `pid_roundtrip` | pid | the example spec drawn through `pid_from_spec`'s code, then read back by the graph builder, which never sees the spec: 5 nodes, 4 edges, 0 dangling ends, `confidence_min` 1.0, zero critique issues, an instrument index of exactly `FIC-101` wired to `FCV-101`, and the two authored line numbers back on the two process lines while the two unnumbered lines carry sequence-built numbers from XDATA | | `pid_roundtrip` | pid | the example spec drawn through `pid_from_spec`'s code, then read back by the graph builder, which never sees the spec: 5 nodes, 4 edges, 0 dangling ends, `confidence_min` 1.0, zero critique issues, an instrument index of exactly `FIC-101` wired to `FCV-101`, and the two authored line numbers back on the two process lines while the two unnumbered lines carry sequence-built numbers from XDATA |
| `page_setup_truth` | pagesetup | `page_setup_apply("Layout1", ISO_A3 landscape 1:1)` then `batch_plot`; the PDF's own `/MediaBox` must parse to 420 × 297 mm (±0.5), the page-setup list must say `ISO_A3` / `landscape` / `[420, 297]`, and the ctb must be a known one |
The competitor reports carry no result for it, for the same reason they carry The competitor reports carry no result for either, for the same reason they
none for the v3 five. `--matrix v3` reproduces the v1.5 set exactly. carry none for the v3 five. `--matrix v3` reproduces the v1.5 set exactly.
## Live competitor lane (v1.4) ## Live competitor lane (v1.4)
@@ -216,19 +217,24 @@ python benchmarks/compare_versions.py --json results.json
### Result — this branch (1.6.0-dev) vs v1.5.1 (release gate) ### Result — this branch (1.6.0-dev) vs v1.5.1 (release gate)
29 checks, ezdxf backend, one subprocess per check. Machine-readable report: 32 checks, ezdxf backend, one subprocess per check. Machine-readable report:
[`results/published/ab-v1.5.1-vs-v1.6.0-dev.json`](results/published/ab-v1.5.1-vs-v1.6.0-dev.json). [`results/published/ab-v1.5.1-vs-v1.6.0-dev.json`](results/published/ab-v1.5.1-vs-v1.6.0-dev.json).
| Version | Checks passing | Pass rate | Fixed | Regressed | | Version | Checks passing | Pass rate | Fixed | Regressed |
|---------|----------------|-----------|-------|-----------| |---------|----------------|-----------|-------|-----------|
| **v1.5.1** (baseline) | 26 / 29 | 89.7 % | — | — | | **v1.5.1** (baseline) | 26 / 32 | 81.2 % | — | — |
| **v1.6.0-dev** (this branch) | 29 / 29 | 100 % | 3 | **0** | | **v1.6.0-dev** (this branch) | 32 / 32 | 100 % | 6 | **0** |
The three are the P&ID checks Track A added — `pid_block_define_attdef_roundtrip`, The six are all `miss → pass` — v1.5.1 has none of the methods: the three
`pid_tag_parse_fic`, `pid_graph_edge_count` — all `miss → pass`: v1.5.1 has P&ID checks track A added (`pid_block_define_attdef_roundtrip`,
none of the methods. Every one of the 26 checks it was released on still `pid_tag_parse_fic`, `pid_graph_edge_count`) and the three settings checks of
passes, so the track added capability without moving a number it had already track E (`settings_dimstyle_iso25_values`: the ISO-25 preset reaches the
earned. DIMSTYLE table with ISO 129-1's numbers, read back through ezdxf and not
through the lister; `settings_layer_state_roundtrip`: save → change → restore
puts the layer table back and the state survives save/reopen as an XRECORD in
the file; `settings_pdf_mediabox_a3`: the plotted PDF's own `/MediaBox` reads
420 × 297 mm). Every one of the 26 checks it was released on still passes, so
the two tracks added capability without moving a number it had already earned.
### Result — v1.5.1 vs v1.5.0 ### Result — v1.5.1 vs v1.5.0
+48
View File
@@ -431,6 +431,54 @@ class AutoCADMCPProAdapter(BenchmarkAdapter):
[], [],
) )
async def _task_page_setup_truth(self):
"""ISO A3 landscape on Layout1, plotted, and the sheet read back from the PDF.
The number that matters is parsed from the file's ``/MediaBox`` — the
setter's ``applied`` block is what we asked for, the page-setup list is
what the LAYOUT now says, and the PDF is what a printer would get. All
three have to agree with ISO 216's 420 × 297.
"""
from engineering.standards.papers import resolve_page_setup
from engineering.standards.plot import batch_plot
setup = resolve_page_setup(
"ISO_A3",
orientation="landscape",
plot_style="monochrome.ctb",
scale="1:1",
)
applied = await self.backend.page_setup_apply("Layout1", setup)
listed = (await self.backend.page_setup_list("Layout1"))[0]
out_dir = self.artifact_dir / "page_setup_truth"
out_dir.mkdir(parents=True, exist_ok=True)
report = await batch_plot(self.backend, ["Layout1"], str(out_dir), "{drawing}-{layout}.pdf")
row = report["sheets"][0]
width_mm, height_mm = (float(v) for v in row["mediabox_mm"])
size_listed = tuple(float(v) for v in listed["size_mm"])
passed = (
applied["ok"] is True
and applied["plot_style_known"] is True
and listed["paper"] == "ISO_A3"
and listed["orientation"] == "landscape"
and size_listed == (420.0, 297.0)
and abs(width_mm - 420.0) < 0.5
and abs(height_mm - 297.0) < 0.5
and row["bytes"] > 0
)
return (
passed,
{
"paper": listed["paper"],
"orientation": listed["orientation"],
"size_mm_listed": list(size_listed),
"mediabox_mm": [width_mm, height_mm],
"pdf_bytes": row["bytes"],
"plot_style_known": applied["plot_style_known"],
},
[str(row["path"])],
)
async def _task_auditable_delivery(self): async def _task_auditable_delivery(self):
await ensure_engineering_layers(self.backend) await ensure_engineering_layers(self.backend)
await self.backend.entity_create_line(0, 0, 10, 0, layer="GEOMETRY") await self.backend.entity_create_line(0, 0, 10, 0, layer="GEOMETRY")
+71
View File
@@ -321,6 +321,74 @@ async def pid_graph_edge_count():
return len(result["graph"]["edges"]) == 4 and result["graph"]["stats"]["dangling"] == 0 return len(result["graph"]["edges"]) == 4 and result["graph"]["stats"]["dangling"] == 0
# ── Settings and environment (v1.6, track E) ─────────────────────────────────
async def settings_dimstyle_iso25_values():
"""The ISO-25 preset reaches the DIMSTYLE table with ISO 129-1's numbers.
Read back through ezdxf directly, not through `dimstyle_list`, so the
check cannot be satisfied by a list that echoes its own input.
"""
from engineering.standards.dimstyles import resolve_dimstyle
b = await _b()
await b.dimstyle_create("ISO-25", resolve_dimstyle("iso-25", None), set_current=True)
style = b._doc.dimstyles.get("ISO-25")
listed = {row["name"]: row for row in await b.dimstyle_list()}
return (
abs(float(style.dxf.dimtxt) - 2.5) < 1e-9
and abs(float(style.dxf.dimasz) - 2.5) < 1e-9
and abs(float(style.dxf.dimexe) - 1.25) < 1e-9
and abs(float(style.dxf.dimexo) - 0.625) < 1e-9
and abs(float(style.dxf.dimgap) - 0.625) < 1e-9
and int(style.dxf.dimtad) == 1
and int(style.dxf.dimdsep) == ord(",")
and str(style.dxf.dimtxsty) == "ISOCP"
and b._doc.header["$DIMSTYLE"] == "ISO-25"
and listed["ISO-25"]["current"] is True
and listed["ISO-25"]["values"]["DIMDSEP"] == ","
)
async def settings_layer_state_roundtrip():
"""Save → change → restore puts the table back, and the state survives a
save/reopen because it is an XRECORD in the file, not server memory."""
b = await _b()
await b.layer_create("LS_A", color=1)
await b.layer_create("LS_B", color=2)
saved = await b.layer_state_save("bench", description="A/B round trip")
await b.layer_modify("LS_A", color=5)
await b.layer_freeze("LS_B")
restored = await b.layer_state_restore("bench")
layers = {layer.name: layer for layer in await b.layer_list()}
path = os.path.join(tempfile.mkdtemp(), "ls.dxf")
await b.drawing_save_as(path, "dxf")
await b.drawing_open(path)
names_after_reopen = {row["name"] for row in await b.layer_state_list()}
return (
saved["ok"] is True
and restored["missing_layers"] == []
and restored["new_layers"] == []
and layers["LS_A"].color == 1
and layers["LS_B"].is_frozen is False
and "bench" in names_after_reopen
)
async def settings_pdf_mediabox_a3():
"""Page setup evidence is the PDF's own /MediaBox (spec §8.3)."""
from engineering.standards.papers import resolve_page_setup
from engineering.standards.plot import batch_plot
b = await _b()
await b.page_setup_apply("Layout1", resolve_page_setup("ISO_A3"))
out_dir = tempfile.mkdtemp()
report = await batch_plot(b, ["Layout1"], out_dir, "{drawing}-{layout}.pdf")
width_mm, height_mm = report["sheets"][0]["mediabox_mm"]
return abs(width_mm - 420.0) < 0.5 and abs(height_mm - 297.0) < 0.5
CHECKS = { CHECKS = {
"core_line_length": (core_line_length, "Core"), "core_line_length": (core_line_length, "Core"),
"core_circle_radius": (core_circle_radius, "Core"), "core_circle_radius": (core_circle_radius, "Core"),
@@ -351,6 +419,9 @@ CHECKS = {
"pid_block_define_attdef_roundtrip": (pid_block_define_attdef_roundtrip, "P&ID"), "pid_block_define_attdef_roundtrip": (pid_block_define_attdef_roundtrip, "P&ID"),
"pid_tag_parse_fic": (pid_tag_parse_fic, "P&ID"), "pid_tag_parse_fic": (pid_tag_parse_fic, "P&ID"),
"pid_graph_edge_count": (pid_graph_edge_count, "P&ID"), "pid_graph_edge_count": (pid_graph_edge_count, "P&ID"),
"settings_dimstyle_iso25_values": (settings_dimstyle_iso25_values, "Settings"),
"settings_layer_state_roundtrip": (settings_layer_state_roundtrip, "Settings"),
"settings_pdf_mediabox_a3": (settings_pdf_mediabox_a3, "Settings"),
} }
@@ -1,12 +1,12 @@
{ {
"summary": { "summary": {
"baseline_ref": "v1.5.1", "baseline_ref": "v1.5.1",
"total": 29, "total": 32,
"old_pass": 26, "old_pass": 26,
"new_pass": 29, "new_pass": 32,
"old_pct": 89.7, "old_pct": 81.2,
"new_pct": 100.0, "new_pct": 100.0,
"fixed": 3, "fixed": 6,
"regressed": 0 "regressed": 0
}, },
"rows": [ "rows": [
@@ -183,6 +183,24 @@
"category": "P&ID", "category": "P&ID",
"old": "miss", "old": "miss",
"new": "pass" "new": "pass"
},
{
"check": "settings_dimstyle_iso25_values",
"category": "Settings",
"old": "miss",
"new": "pass"
},
{
"check": "settings_layer_state_roundtrip",
"category": "Settings",
"old": "miss",
"new": "pass"
},
{
"check": "settings_pdf_mediabox_a3",
"category": "Settings",
"old": "miss",
"new": "pass"
} }
] ]
} }
+19 -7
View File
@@ -1,10 +1,16 @@
"""The v4 task matrix: v3 plus the P&ID round trip (v1.6, track A). """The v4 task matrix: v3 plus what 1.6 added.
``pid_roundtrip`` draws a five-item P&ID through the same code the tools call ``pid_roundtrip`` (track A) draws a five-item P&ID through the same code the
and reads it back: the edge count, the absence of dangling ends and the tools call and reads it back: the edge count, the absence of dangling ends
instrument index are each derived independently of the drawing code. The and the instrument index are each derived independently of the drawing code.
published competitor reports were never asked this question; the chart shows
``not_run`` for them, not zero. ``page_setup_truth`` (track E) applies an ISO A3 landscape page setup to a
layout, plots it through ``batch_plot`` and reads the sheet size back from
the PDF's own ``/MediaBox`` — the setup is verified by the file, not by the
setter's return value.
The published competitor reports were never asked either question; the chart
shows ``not_run`` for them, not zero.
""" """
from __future__ import annotations from __future__ import annotations
@@ -12,7 +18,7 @@ from __future__ import annotations
from benchmarks.tasks_v2 import TASKS_V2, TaskSpec from benchmarks.tasks_v2 import TASKS_V2, TaskSpec
from benchmarks.tasks_v3 import TASKS_V3 from benchmarks.tasks_v3 import TASKS_V3
#: Added in v1.6.0. The reader can fail this one on its own. #: Added in v1.6.0. Each can fail on its own.
NEW_TASKS_V4: tuple[TaskSpec, ...] = ( NEW_TASKS_V4: tuple[TaskSpec, ...] = (
TaskSpec( TaskSpec(
"pid_roundtrip", "pid_roundtrip",
@@ -20,6 +26,12 @@ NEW_TASKS_V4: tuple[TaskSpec, ...] = (
"Draw a P&ID from a spec and read back a matching graph, index and line list", "Draw a P&ID from a spec and read back a matching graph, index and line list",
1.0, 1.0,
), ),
TaskSpec(
"page_setup_truth",
"pagesetup",
"Apply an ISO A3 page setup, plot it, and read the sheet size back from the PDF",
1.0,
),
) )
TASKS_V4: tuple[TaskSpec, ...] = TASKS_V3 + NEW_TASKS_V4 TASKS_V4: tuple[TaskSpec, ...] = TASKS_V3 + NEW_TASKS_V4
+42 -8
View File
@@ -1,23 +1,28 @@
"""Track A — the v4 task matrix. # tests/test_benchmark_v4.py
"""The v4 task matrix: v3 plus what 1.6 added.
v4 adds one task, ``pid_roundtrip``: draw a P&ID through the same code the Track A added ``pid_roundtrip``; track E adds ``page_setup_truth`` — apply an
tools call and read it back through the graph builder, so the edge count, the ISO A3 landscape page setup, plot through ``batch_plot`` and read the sheet
dangling count and the instrument index are each derived independently of the size back from the PDF's own ``/MediaBox``. The published competitor reports
drawing code. The published competitor reports were never asked this question were never asked either question and the chart shows them ``not_run`` — the
and the chart shows them ``not_run`` — the same rule v3 set for its five. same rule v3 set for its five.
""" """
from __future__ import annotations from __future__ import annotations
import pytest
from benchmarks.tasks_v3 import TASKS_V3 from benchmarks.tasks_v3 import TASKS_V3
from benchmarks.tasks_v4 import DEFAULT_MATRIX, MATRICES, NEW_TASKS_V4, TASKS_V4, task_by_id from benchmarks.tasks_v4 import DEFAULT_MATRIX, MATRICES, NEW_TASKS_V4, TASKS_V4, task_by_id
def test_v4_extends_v3_without_touching_it(): def test_v4_extends_v3_without_touching_it():
assert TASKS_V4[: len(TASKS_V3)] == TASKS_V3 assert TASKS_V4[: len(TASKS_V3)] == TASKS_V3
assert [t.task_id for t in NEW_TASKS_V4] == ["pid_roundtrip"] 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 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("pid_roundtrip").category == "pid"
assert task_by_id("page_setup_truth").category == "pagesetup"
def test_the_adapter_implements_every_v4_task(): def test_the_adapter_implements_every_v4_task():
@@ -32,4 +37,33 @@ def test_correctness_suite_has_the_pid_checks():
for name in ("pid_block_define_attdef_roundtrip", "pid_tag_parse_fic", "pid_graph_edge_count"): for name in ("pid_block_define_attdef_roundtrip", "pid_tag_parse_fic", "pid_graph_edge_count"):
assert CHECKS[name][1] == "P&ID" assert CHECKS[name][1] == "P&ID"
assert len(CHECKS) == 29
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"
assert len(CHECKS) == 32
@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