"""scripts/ 配下スクリプトの起動スモークテスト.
目的:
- 各スクリプトはプロジェクトルートを自前で sys.path に追加し,
リポジトリ外のカレントディレクトリからでも実行できること(SPEC_01 の scripts/ 実行形態)
- import 整理(ruff の I001 自動修正等)で `from src...` が sys.path 追加より前へ
移動するような回帰を検出する
方針:
- argparse を持つスクリプトは `--help` で起動し終了コード 0 を確認する
- argparse を持たないスクリプトはモジュールとして読み込めること(import 成功)のみ確認する
- viewer.py は streamlit アプリのため対象外
- いずれもサブプロセスで実行し,cwd はリポジトリ外(tmp_path),PYTHONPATH は除去する
"""
import os
import subprocess
import sys
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
SCRIPTS_DIR = PROJECT_ROOT / "scripts"
EXCLUDED_SCRIPTS = {"viewer.py", "__init__.py"}
SCRIPT_PATHS = sorted(
path for path in SCRIPTS_DIR.glob("*.py") if path.name not in EXCLUDED_SCRIPTS
)
TIMEOUT_SEC = 120
IMPORT_SNIPPET = (
"import importlib.util, sys\n"
"spec = importlib.util.spec_from_file_location('smoke_target', sys.argv[1])\n"
"module = importlib.util.module_from_spec(spec)\n"
"spec.loader.exec_module(module)\n"
)
def _clean_env() -> dict[str, str]:
"""リポジトリ由来の import 経路を持ち込まないよう PYTHONPATH を除いた環境変数を返す."""
env = {key: value for key, value in os.environ.items() if key != "PYTHONPATH"}
env["MPLBACKEND"] = "Agg"
return env
def _uses_argparse(script_path: Path) -> bool:
return "argparse" in script_path.read_text(encoding="utf-8")
def test_scripts_are_discovered() -> None:
"""対象スクリプトが 1 件以上見つかること(glob の設定ミスで全件スキップされるのを防ぐ)."""
assert len(SCRIPT_PATHS) > 0
@pytest.mark.parametrize("script_path", SCRIPT_PATHS, ids=lambda path: path.name)
def test_script_starts_from_outside_repository(script_path: Path, tmp_path: Path) -> None:
"""リポジトリ外の cwd から起動(--help)または import できること."""
if _uses_argparse(script_path):
command = [sys.executable, str(script_path), "--help"]
else:
command = [sys.executable, "-c", IMPORT_SNIPPET, str(script_path)]
result = subprocess.run(
command,
cwd=tmp_path,
env=_clean_env(),
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=TIMEOUT_SEC,
)
assert result.returncode == 0, (
f"{script_path.name} failed (returncode={result.returncode})\n"
f"stderr:\n{result.stderr}"
)
if _uses_argparse(script_path):
assert "usage" in result.stdout.lower()