Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions run_local_smoke_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
from __future__ import annotations

import argparse
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Sequence


DEFAULT_TESTS = (
"tests/test_release_candidate_check.py",
"tests/test_runtime_readiness.py",
"tests/test_ui_unified_persona_pipeline.py",
"tests/test_stockfish_service_recovery.py",
"tests/test_main_ui_elite_recommendation.py",
)


@dataclass(frozen=True)
class SmokeStep:
name: str
command: tuple[str, ...]
required: bool = True


def run_command(
step: SmokeStep,
*,
project_root: Path,
) -> int:
print()
print("=" * 72)
print(step.name)
print("=" * 72)
print(" ".join(step.command))

completed = subprocess.run(
step.command,
cwd=project_root,
text=True,
encoding="utf-8",
errors="replace",
)

if completed.returncode == 0:
print(f"[OK] {step.name}")
else:
print(f"[FAILED] {step.name} exit code {completed.returncode}")

return int(completed.returncode)


def build_steps(
*,
python_executable: str,
engine_path: str,
strict_release_check: bool,
tests: Sequence[str],
) -> tuple[SmokeStep, ...]:
release_command = [
python_executable,
"check_release_candidate.py",
"--engine-path",
engine_path,
]

if not strict_release_check:
release_command.append("--allow-dirty-code")

pytest_command = [
python_executable,
"-m",
"pytest",
*tests,
"-q",
]

return (
SmokeStep(
name="Release candidate safety check",
command=tuple(release_command),
required=True,
),
SmokeStep(
name="Focused smoke test suite",
command=tuple(pytest_command),
required=True,
),
)


def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description=(
"Run the minimal local smoke checks for ChessPersona before a "
"merge, release candidate, or stable tag."
)
)
parser.add_argument(
"--project-root",
default=".",
)
parser.add_argument(
"--engine-path",
default="engines/stockfish/stockfish.exe",
)
parser.add_argument(
"--strict-release-check",
action="store_true",
help=(
"Do not allow dirty source files in the release candidate check. "
"Use this after everything is committed."
),
)
parser.add_argument(
"--test",
action="append",
dest="tests",
help=(
"Override focused pytest target. Can be passed more than once. "
"Default uses the stable MVP smoke targets."
),
)
return parser


def main(argv: Sequence[str] | None = None) -> int:
parser = build_arg_parser()
args = parser.parse_args(argv)

project_root = Path(args.project_root).resolve()
tests = tuple(args.tests or DEFAULT_TESTS)

steps = build_steps(
python_executable=sys.executable,
engine_path=str(args.engine_path),
strict_release_check=bool(args.strict_release_check),
tests=tests,
)

failed: list[SmokeStep] = []

print("ChessPersona Local Smoke Tests")
print("=" * 72)
print(f"Project root : {project_root}")
print(f"Python : {sys.executable}")
print(f"Engine path : {args.engine_path}")
print(f"Strict check : {bool(args.strict_release_check)}")

for step in steps:
code = run_command(
step,
project_root=project_root,
)
if code != 0 and step.required:
failed.append(step)

print()
print("=" * 72)
if failed:
print("SMOKE TEST RESULT: FAILED")
for step in failed:
print(f"- {step.name}")
print()
print("Fix the failed step above before merging or tagging.")
return 2

print("SMOKE TEST RESULT: PASSED")
print("Local MVP checks are green.")
return 0


if __name__ == "__main__":
raise SystemExit(main())
50 changes: 50 additions & 0 deletions tests/test_local_smoke_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from __future__ import annotations

from run_local_smoke_tests import (
DEFAULT_TESTS,
build_steps,
)


def test_build_steps_uses_allow_dirty_by_default() -> None:
steps = build_steps(
python_executable="python",
engine_path="engines/stockfish/stockfish.exe",
strict_release_check=False,
tests=("tests/test_runtime_readiness.py",),
)

release_step = steps[0]

assert release_step.name == "Release candidate safety check"
assert "--allow-dirty-code" in release_step.command


def test_build_steps_can_be_strict_after_commit() -> None:
steps = build_steps(
python_executable="python",
engine_path="engines/stockfish/stockfish.exe",
strict_release_check=True,
tests=("tests/test_runtime_readiness.py",),
)

release_step = steps[0]

assert "--allow-dirty-code" not in release_step.command


def test_build_steps_runs_focused_pytest_targets() -> None:
steps = build_steps(
python_executable="python",
engine_path="engine.exe",
strict_release_check=False,
tests=DEFAULT_TESTS,
)

pytest_step = steps[1]

assert pytest_step.name == "Focused smoke test suite"
assert pytest_step.command[:3] == ("python", "-m", "pytest")
assert "tests/test_runtime_readiness.py" in pytest_step.command
assert "tests/test_ui_unified_persona_pipeline.py" in pytest_step.command
assert pytest_step.command[-1] == "-q"