mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 18:47:04 +01:00
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
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
138 lines
4.2 KiB
Python
138 lines
4.2 KiB
Python
"""Docker plumbing with ``subprocess.run`` mocked: no daemon needed."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from contextlib import AbstractContextManager
|
|
import shutil
|
|
import subprocess
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from python_pkg.sql_drills import _engine
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterator
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _fake_docker_on_path() -> Iterator[None]:
|
|
with patch.object(shutil, "which", return_value="docker"):
|
|
yield
|
|
|
|
|
|
def _cp(rc: int = 0, out: str = "", err: str = "") -> subprocess.CompletedProcess[str]:
|
|
return subprocess.CompletedProcess([], rc, out, err)
|
|
|
|
|
|
def _run(
|
|
*results: subprocess.CompletedProcess[str],
|
|
) -> AbstractContextManager[MagicMock | AsyncMock]:
|
|
return patch.object(subprocess, "run", side_effect=list(results))
|
|
|
|
|
|
def test_docker_missing_binary_is_engine_error() -> None:
|
|
with (
|
|
patch.object(shutil, "which", return_value=None),
|
|
pytest.raises(_engine.EngineError, match="docker"),
|
|
):
|
|
_engine.container_state()
|
|
|
|
|
|
def test_container_state_running() -> None:
|
|
with _run(_cp(0, "running\n")):
|
|
assert _engine.container_state() == "running"
|
|
|
|
|
|
def test_container_state_missing_on_inspect_failure() -> None:
|
|
with _run(_cp(1, "", "No such object")):
|
|
assert _engine.container_state() == "missing"
|
|
|
|
|
|
def test_is_ready_follows_ping_exit_code() -> None:
|
|
with _run(_cp(0)):
|
|
assert _engine.is_ready()
|
|
with _run(_cp(1)):
|
|
assert not _engine.is_ready()
|
|
|
|
|
|
def test_start_creates_container_when_missing_then_waits() -> None:
|
|
with _run(_cp(1), _cp(0, "id"), _cp(1), _cp(0)) as run:
|
|
_engine.start(timeout=5, sleep=lambda _s: None)
|
|
run_args = run.call_args_list[1].args[0]
|
|
assert run_args[:3] == ["docker", "run", "-d"]
|
|
assert "--tmpfs" in run_args
|
|
assert _engine.IMAGE in run_args
|
|
assert "--name" in run_args
|
|
assert _engine.CONTAINER in run_args
|
|
|
|
|
|
def test_start_resumes_exited_container() -> None:
|
|
with _run(_cp(0, "exited\n"), _cp(0), _cp(0)) as run:
|
|
_engine.start(timeout=5, sleep=lambda _s: None)
|
|
assert run.call_args_list[1].args[0][:2] == ["docker", "start"]
|
|
|
|
|
|
def test_start_when_already_running_only_waits() -> None:
|
|
with _run(_cp(0, "running\n"), _cp(0)) as run:
|
|
_engine.start(timeout=5, sleep=lambda _s: None)
|
|
assert run.call_count == 2
|
|
|
|
|
|
def test_start_run_failure_is_reported() -> None:
|
|
with (
|
|
_run(_cp(1), _cp(125, "", "pull access denied")),
|
|
pytest.raises(_engine.EngineError, match="pull access denied"),
|
|
):
|
|
_engine.start(timeout=5, sleep=lambda _s: None)
|
|
|
|
|
|
def test_start_times_out_with_rerun_hint() -> None:
|
|
ticks = iter([0.0, 1.0, 2.0, 99.0, 100.0])
|
|
results = [_cp(0, "running\n")] + [_cp(1)] * 10
|
|
with _run(*results), pytest.raises(_engine.EngineError, match="rerun"):
|
|
_engine.start(timeout=10, sleep=lambda _s: None, clock=lambda: next(ticks))
|
|
|
|
|
|
def test_stop_removes_container() -> None:
|
|
with _run(_cp(0)) as run:
|
|
_engine.stop()
|
|
assert run.call_args.args[0] == ["docker", "rm", "-f", _engine.CONTAINER]
|
|
|
|
|
|
def test_run_sql_passes_stdin_and_database() -> None:
|
|
with _run(_cp(0, "1\n", "")) as run:
|
|
res = _engine.run_sql("SELECT 1;", database="d1")
|
|
argv = run.call_args.args[0]
|
|
assert argv[:3] == ["docker", "exec", "-i"]
|
|
assert "-B" in argv
|
|
assert "-N" in argv
|
|
assert argv[-1] == "d1"
|
|
assert run.call_args.kwargs["input"] == "SELECT 1;"
|
|
assert res.returncode == 0
|
|
assert res.stdout == "1\n"
|
|
|
|
|
|
def test_run_sql_without_database() -> None:
|
|
with _run(_cp(0)) as run:
|
|
_engine.run_sql("SELECT 1;")
|
|
assert run.call_args.args[0][-1] == "-N"
|
|
|
|
|
|
def test_run_sql_timeout_becomes_engine_error() -> None:
|
|
boom = subprocess.TimeoutExpired(cmd="docker", timeout=1)
|
|
with (
|
|
patch.object(subprocess, "run", side_effect=boom),
|
|
pytest.raises(_engine.EngineError, match="timed out"),
|
|
):
|
|
_engine.run_sql("SELECT SLEEP(99);", timeout=1)
|
|
|
|
|
|
def test_run_sql_missing_docker_is_not_relabelled() -> None:
|
|
with (
|
|
patch.object(shutil, "which", return_value=None),
|
|
pytest.raises(_engine.EngineError, match="docker binary"),
|
|
):
|
|
_engine.run_sql("SELECT 1;")
|