testsAndMisc/python_pkg/sql_drills/_check.py
Krzysztof kuhy Rudnicki ee78ae44cc
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Add sql_drills MySQL exact-match harness
Docker mysql:8.0.33 container, judge-style output grading, 12 original drills with live-verified references kept in tests only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWZewj4WS5X6AZyjWDp
2026-11-01 21:13:18 +01:00

68 lines
2.2 KiB
Python

"""Grade one query: fresh database, drill schema, user's SQL, exact compare."""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
from python_pkg.sql_drills import _engine
from python_pkg.sql_drills._normalize import diff, normalize
if TYPE_CHECKING:
from pathlib import Path
from python_pkg.sql_drills._drills import Drill
@dataclass(frozen=True)
class CheckResult:
"""``status`` is ``pass``, ``fail`` (wrong output) or ``error``."""
status: str
message: str
class _StepError(Exception):
"""A pipeline step failed; the message is shown to the user."""
def _step(result: _engine.SqlResult, what: str) -> str:
"""Return stdout of a successful step or raise with its stderr."""
if result.returncode != 0:
msg = f"{what}: {result.stderr.strip()}"
raise _StepError(msg)
return result.stdout
def _execute(drill: Drill, query_file: Path) -> str:
"""Fresh DB, drill schema, then the user's SQL; return the raw output."""
if not _engine.is_ready():
msg = "MySQL is not running; run `start` first"
raise _StepError(msg)
if not query_file.is_file():
msg = f"query file not found: {query_file}"
raise _StepError(msg)
db = f"drill_{drill.id}"
reset = _engine.run_sql(f"DROP DATABASE IF EXISTS {db}; CREATE DATABASE {db};")
_step(reset, "could not reset database")
_step(_engine.run_sql(drill.schema.read_text(), database=db), "drill schema failed")
result = _engine.run_sql(query_file.read_text(), database=db)
return _step(result, "SQL error")
def run_check(drill: Drill, query_file: Path) -> CheckResult:
"""Load the drill into a fresh DB, run the query, compare to expected."""
try:
output = _execute(drill, query_file)
except _StepError as exc:
return CheckResult("error", str(exc))
expected = normalize(drill.expected.read_text())
actual = normalize(output)
if expected == actual:
return CheckResult("pass", f"PASS {drill.id} ({len(actual)} rows)")
return CheckResult(
"fail",
f"FAIL {drill.id}: expected {len(expected)} rows, got {len(actual)}\n"
f"{diff(expected, actual)}",
)