From dd1b77cd9cc973adb32e77ed835fdd927573f289 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Wed, 11 Nov 2026 22:42:33 +0000 Subject: [PATCH] fix(syntax-receipts): import execute runs that have no run database A capture run that executes each case once in a fresh session, without a run-owned database, names its capture in the start event but uses neither the scripting submission nor the compile-only EXPLAIN method, so the importer refused the whole run as matching no known driver. The packet driver already reads such runs, with an empty setup; it now handles them. --- .../syntax_receipts/_helpers/packet_run.py | 2 +- scripts/syntax_receipts/_helpers/runs.py | 8 +++- scripts/syntax_receipts/constants.py | 1 + .../scripts/syntax_receipts/_test_types.py | 8 ++++ .../syntax_receipts/test_import_runs.py | 38 +++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/scripts/syntax_receipts/_helpers/packet_run.py b/scripts/syntax_receipts/_helpers/packet_run.py index e1b9b5b8d..9dca49a11 100644 --- a/scripts/syntax_receipts/_helpers/packet_run.py +++ b/scripts/syntax_receipts/_helpers/packet_run.py @@ -1,4 +1,4 @@ -"""Anonymous-block and compile-only EXPLAIN runs: one raw line per packet row or statement.""" +"""Anonymous-block, execute and EXPLAIN runs: a raw line per packet row or statement.""" from __future__ import annotations diff --git a/scripts/syntax_receipts/_helpers/runs.py b/scripts/syntax_receipts/_helpers/runs.py index 6dcc2d4b6..1d48b67f9 100644 --- a/scripts/syntax_receipts/_helpers/runs.py +++ b/scripts/syntax_receipts/_helpers/runs.py @@ -12,8 +12,10 @@ from scripts.syntax_receipts._helpers.packet_run import import_packet_run from scripts.syntax_receipts._helpers.receipts import refusal from scripts.syntax_receipts.constants import ( + CAPTURE_EXECUTE, EXPLAIN_METHOD, MANIFEST_KEY, + PACKET_KEY, PROPOSAL_KEY, RECONCILED_STATUS, RUN_DATABASE_KEY, @@ -58,7 +60,11 @@ def _import_run(*, run: CaptureRun, index: dict[str, Path]) -> Imported: start: Row = run.events[0] if RUN_DATABASE_KEY in start: return import_ddl_run(run=run, index=index) - if start.get("submission") == SCRIPTING_SUBMISSION or start.get("method") == EXPLAIN_METHOD: + if ( + start.get("submission") == SCRIPTING_SUBMISSION + or start.get("method") == EXPLAIN_METHOD + or (start.get("capture") == CAPTURE_EXECUTE and PACKET_KEY in start) + ): return import_packet_run(run=run, index=index) if PROPOSAL_KEY in start: return import_diagnostic_run(run=run) diff --git a/scripts/syntax_receipts/constants.py b/scripts/syntax_receipts/constants.py index 347f59871..acec7c399 100644 --- a/scripts/syntax_receipts/constants.py +++ b/scripts/syntax_receipts/constants.py @@ -59,6 +59,7 @@ RUN_DATABASE_KEY: str = "run_database" PROPOSAL_KEY: str = "proposal_sha256" MANIFEST_KEY: str = "manifest_sha256" +PACKET_KEY: str = "packet_sha256" PACKET_ACCEPTED_OUTCOMES: tuple[str, ...] = ("accepted_executed", "compiled") DIAGNOSTIC_ACCEPTED_OUTCOME: str = "rows" CANONICAL_ACCEPTED_OUTCOMES: tuple[str, ...] = ("rows", "types") diff --git a/tests/unit/scripts/syntax_receipts/_test_types.py b/tests/unit/scripts/syntax_receipts/_test_types.py index 6242955a0..dd835abb1 100644 --- a/tests/unit/scripts/syntax_receipts/_test_types.py +++ b/tests/unit/scripts/syntax_receipts/_test_types.py @@ -39,6 +39,14 @@ class RefusedRunTestCase: expected_reason_fragment: str +@dataclass(frozen=True) +class CaptureDriverTestCase: + description: str + start: Row + expected_status: str + expected_capture_methods: list[str] + + @dataclass(frozen=True) class OverwriteTestCase: description: str diff --git a/tests/unit/scripts/syntax_receipts/test_import_runs.py b/tests/unit/scripts/syntax_receipts/test_import_runs.py index da648d1d7..1e9da3dfa 100644 --- a/tests/unit/scripts/syntax_receipts/test_import_runs.py +++ b/tests/unit/scripts/syntax_receipts/test_import_runs.py @@ -16,6 +16,7 @@ from scripts.syntax_receipts.types import Row from tests.unit.scripts.syntax_receipts._test_types import ( BindingTestCase, + CaptureDriverTestCase, ClassifyTestCase, OverwriteTestCase, QueryHistoryReceiptTestCase, @@ -333,6 +334,43 @@ def test_given_raw_changed_after_audit_when_imported_then_the_whole_run_is_refus assert test_case.expected_reason_fragment in imported.runs[0]["reason"], test_case.description +@pytest.mark.parametrize( + "test_case", + ( + CaptureDriverTestCase( + description="an execute run, one fresh session per case and no run database", + start={ + "capture": "execute", + "submission": "as-is, once, a fresh session per case, ROLLBACK and close after", + }, + expected_status="imported", + expected_capture_methods=["execute"], + ), + CaptureDriverTestCase( + description="a start event naming no driver", + start={"submission": "as-is, once"}, + expected_status="refused", + expected_capture_methods=[], + ), + ), + ids=lambda case: case.description, +) +def test_given_a_start_event_when_imported_then_its_driver_reads_the_run( + test_case: CaptureDriverTestCase, tmp_path: Path +) -> None: + write_run( + root=tmp_path, + rows=[_SYNTAX_ROW], + packet=[packet_case(case_id="c1", sql=_GOOD)], + start=test_case.start, + ) + + imported: Imported = import_runs(runs=tmp_path / "runs", input_dirs=[tmp_path / "inputs"]) + + assert imported.runs[0]["status"] == test_case.expected_status + assert [r["capture_method"] for r in imported.receipts] == test_case.expected_capture_methods + + @pytest.mark.parametrize( "test_case", (