Newer
Older
TIASjudge / tests / test_package_skeleton.py
"""パッケージ骨格の検証(PLAN_02 ステップ 1 / project-structure.md).

仕様上要求されるのは以下.

- `src/tiasjudge/` の src レイアウト.配下に `core/`(診断ロジック)と `gui/`(PySide6 画面)
- `python -m tiasjudge` で起動できるエントリポイント(`__main__.py`)
- 同梱データ `data/` と テスト `tests/`
"""

import importlib
import subprocess
import sys
from pathlib import Path

import pytest


class TestDirectoryLayout:
    """ディレクトリ構成(project-structure.md)."""

    @pytest.mark.parametrize(
        "relative_path",
        [
            "src/tiasjudge",
            "src/tiasjudge/core",
            "src/tiasjudge/gui",
            "data",
            "tests",
            "docs",
        ],
    )
    def test_required_directories_exist(self, repo_root: Path, relative_path: str) -> None:
        assert (repo_root / relative_path).is_dir()

    @pytest.mark.parametrize(
        "relative_path",
        [
            "pyproject.toml",
            "src/tiasjudge/__init__.py",
            "src/tiasjudge/__main__.py",
            "src/tiasjudge/core/__init__.py",
            "src/tiasjudge/gui/__init__.py",
            "data/data.csv",
            "docs/02_ENV/ENV_04_開発コマンド.md",
        ],
    )
    def test_required_files_exist(self, repo_root: Path, relative_path: str) -> None:
        assert (repo_root / relative_path).is_file()

    def test_org_is_not_tracked_by_git(self, repo_root: Path) -> None:
        """旧実装 `org/` は参照専用で Git 管理外(project-structure.md)."""
        result = subprocess.run(
            ["git", "ls-files", "org"],
            cwd=repo_root,
            capture_output=True,
            text=True,
            check=True,
        )
        assert result.stdout.strip() == ""

    def test_data_csv_is_tracked_or_stageable(self, repo_root: Path) -> None:
        """同梱データ `data/data.csv` は Git 管理対象(無視されていない)."""
        result = subprocess.run(
            ["git", "check-ignore", "data/data.csv"],
            cwd=repo_root,
            capture_output=True,
            text=True,
        )
        assert result.returncode != 0, "data/data.csv が .gitignore で除外されている"


class TestImportablePackages:
    """パッケージが import できること."""

    @pytest.mark.parametrize(
        "module_name",
        ["tiasjudge", "tiasjudge.core", "tiasjudge.gui", "tiasjudge.__main__"],
    )
    def test_import(self, module_name: str) -> None:
        assert importlib.import_module(module_name) is not None

    def test_package_is_installed_from_src_layout(self, repo_root: Path) -> None:
        """import されるパッケージが `src/tiasjudge` であること(editable install)."""
        module = importlib.import_module("tiasjudge")
        assert module.__file__ is not None
        assert Path(module.__file__).resolve().parent == (repo_root / "src" / "tiasjudge").resolve()

    def test_version_is_defined(self) -> None:
        module = importlib.import_module("tiasjudge")
        assert isinstance(module.__version__, str)
        assert module.__version__ != ""


class TestEntryPoint:
    """エントリポイント(ENV_04「起動」).

    メイン画面(SPEC_01)実装後の `main()` は引数なしだとイベントループに入り戻らないため,
    起動可能性の確認には `--check`(画面を構築して即終了)を使う(ENV_04「起動」).
    Qt はヘッドレスで動かす(`QT_QPA_PLATFORM=offscreen`.conftest.py で既定設定).
    """

    def test_main_is_callable_and_returns_exit_code(self) -> None:
        main_module = importlib.import_module("tiasjudge.__main__")
        assert callable(main_module.main)
        assert main_module.main(["--check"]) == 0

    def test_python_m_tiasjudge_check_exits_successfully(
        self, repo_root: Path, headless_env: dict[str, str]
    ) -> None:
        """`python -m tiasjudge --check` が正常終了する(起動可能性の確認)."""
        result = subprocess.run(
            [sys.executable, "-m", "tiasjudge", "--check"],
            cwd=repo_root,
            capture_output=True,
            text=True,
            encoding="utf-8",
            errors="replace",
            env=headless_env,
            timeout=120,
        )
        assert result.returncode == 0, result.stderr