mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 18:07:06 +01:00
sql_drills: optional focus heartbeat
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWYcmik6APnHfc9gEVy
This commit is contained in:
parent
ac9b0b7e97
commit
adc3f72b4b
@ -3,12 +3,14 @@
|
||||
"scope": [
|
||||
"python_pkg/sql_drills (new package, fixtures, tests, README)",
|
||||
"meta/pyproject.toml: register the docker pytest marker",
|
||||
"Non-goal: bumping the pinned mysql image; copying third-party problems"
|
||||
"Non-goal: bumping the pinned mysql image; copying third-party problems",
|
||||
"2026-11-02: show/check send an optional focus-tracker heartbeat (__main__.py, tests/conftest.py, tests/test_focus_hook.py)"
|
||||
],
|
||||
"changes": [
|
||||
"CLI start/stop/list/show/check over one long-lived tmpfs container",
|
||||
"Judge-style normaliser and line diff, unit-tested without Docker",
|
||||
"12 original drills with live-verified reference solutions kept in tests only"
|
||||
"12 original drills with live-verified reference solutions kept in tests only",
|
||||
"_heartbeat(): soft import of focus_project; absent -> no-op, tracker error -> logged, result and exit code unchanged"
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
@ -20,11 +22,17 @@
|
||||
"command": "python3 -m python_pkg.sql_drills check <id> <reference> for all 12 drills",
|
||||
"result": "pass",
|
||||
"evidence": "every drill prints PASS; wrong query prints a line diff, syntax error exits 2"
|
||||
},
|
||||
{
|
||||
"command": "python3 -m pytest -q python_pkg/sql_drills/tests --cov=python_pkg.sql_drills --cov-branch",
|
||||
"result": "pass",
|
||||
"evidence": "61 passed, 15 live tests skipped (no container), 100% branch coverage; show 01 wrote one sql_drills heartbeat row to a scratch FOCUS_PROJECT_DATA"
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"Local MySQL image must be pulled once; start fails with a clear error otherwise",
|
||||
"A runaway query is cut off client-side after 30 s but may keep running in the container until stop"
|
||||
"A runaway query is cut off client-side after 30 s but may keep running in the container until stop",
|
||||
"A hanging tracker would delay show/check; the real one is a local file append"
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the commit; run python3 -m python_pkg.sql_drills stop first",
|
||||
|
||||
@ -3,12 +3,32 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
from python_pkg.sql_drills import _check, _drills, _engine
|
||||
|
||||
_EXIT_CODES = {"pass": 0, "fail": 1, "error": 2}
|
||||
_LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _heartbeat() -> None:
|
||||
"""Tell the optional focus tracker that practice is happening.
|
||||
|
||||
Soft dependency: absent module -> silent no-op; a tracker error is logged
|
||||
(file or registry errors) and never changes the drill's result or
|
||||
exit code.
|
||||
"""
|
||||
try:
|
||||
tracker = importlib.import_module("focus_project")
|
||||
except ImportError:
|
||||
return
|
||||
try:
|
||||
tracker.record_activity("sql_drills", "heartbeat")
|
||||
except OSError, ValueError:
|
||||
_LOG.exception("focus tracker heartbeat failed")
|
||||
|
||||
|
||||
def _out(line: str) -> None:
|
||||
@ -44,8 +64,10 @@ def _dispatch(args: argparse.Namespace) -> int:
|
||||
for drill in _drills.list_drills():
|
||||
_out(f"{drill.id} {_drills.title_of(drill)}")
|
||||
elif args.cmd == "show":
|
||||
_heartbeat()
|
||||
sys.stdout.write(_drills.render_show(_drills.get_drill(args.drill)))
|
||||
else:
|
||||
_heartbeat()
|
||||
result = _check.run_check(_drills.get_drill(args.drill), args.sql_file)
|
||||
_out(result.message)
|
||||
return _EXIT_CODES[result.status]
|
||||
|
||||
14
python_pkg/sql_drills/tests/conftest.py
Normal file
14
python_pkg/sql_drills/tests/conftest.py
Normal file
@ -0,0 +1,14 @@
|
||||
"""Keep the optional focus tracker out of every test unless a test fakes it."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_real_focus_tracker(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Make ``import focus_project`` fail, so no test writes live activity."""
|
||||
monkeypatch.setitem(sys.modules, "focus_project", cast("Any", None))
|
||||
71
python_pkg/sql_drills/tests/test_focus_hook.py
Normal file
71
python_pkg/sql_drills/tests/test_focus_hook.py
Normal file
@ -0,0 +1,71 @@
|
||||
"""The soft-optional focus-tracker heartbeat on ``show`` and ``check``."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import types
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from python_pkg.sql_drills import __main__ as cli
|
||||
from python_pkg.sql_drills import _check
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _fake_tracker(
|
||||
monkeypatch: pytest.MonkeyPatch, *, fail: bool = False
|
||||
) -> list[tuple[Any, ...]]:
|
||||
calls: list[tuple[Any, ...]] = []
|
||||
module = cast("Any", types.ModuleType("focus_project"))
|
||||
|
||||
def record_activity(*args: str) -> None:
|
||||
if fail:
|
||||
msg = "tracker down"
|
||||
raise OSError(msg)
|
||||
calls.append(args)
|
||||
|
||||
module.record_activity = record_activity
|
||||
monkeypatch.setitem(sys.modules, "focus_project", module)
|
||||
return calls
|
||||
|
||||
|
||||
def test_show_sends_heartbeat(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
calls = _fake_tracker(monkeypatch)
|
||||
assert cli.main(["show", "01"]) == 0
|
||||
assert calls == [("sql_drills", "heartbeat")]
|
||||
|
||||
|
||||
def test_check_sends_heartbeat(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
calls = _fake_tracker(monkeypatch)
|
||||
result = _check.CheckResult("fail", "MSG")
|
||||
with patch.object(_check, "run_check", return_value=result):
|
||||
assert cli.main(["check", "01", str(tmp_path / "q.sql")]) == 1
|
||||
assert calls == [("sql_drills", "heartbeat")]
|
||||
|
||||
|
||||
def test_list_sends_no_heartbeat(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
calls = _fake_tracker(monkeypatch)
|
||||
assert cli.main(["list"]) == 0
|
||||
assert calls == []
|
||||
|
||||
|
||||
def test_missing_tracker_changes_nothing(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
assert cli.main(["show", "01"]) == 0
|
||||
assert "CREATE TABLE" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_failing_tracker_is_logged_not_raised(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
_fake_tracker(monkeypatch, fail=True)
|
||||
with caplog.at_level(logging.ERROR):
|
||||
assert cli.main(["show", "01"]) == 0
|
||||
assert "heartbeat" in caplog.text
|
||||
assert "CREATE TABLE" in capsys.readouterr().out
|
||||
Loading…
Reference in New Issue
Block a user