"""`core/` の GUI 非依存の検証(project-structure.md「依存方向のルール」).
- `core/` は PySide6・GUI に依存してはならない(import 禁止)
- 逆方向(`gui/` → `core/`)は許可される
"""
import ast
import subprocess
import sys
from pathlib import Path
FORBIDDEN_ROOTS = {"PySide6", "PyQt5", "PyQt6", "shiboken6"}
def _core_source_files(repo_root: Path) -> list[Path]:
return sorted((repo_root / "src" / "tiasjudge" / "core").rglob("*.py"))
def _imported_roots(source_path: Path) -> set[str]:
"""ファイル内の import 文から,トップレベルのモジュール名を収集する."""
tree = ast.parse(source_path.read_text(encoding="utf-8"), filename=str(source_path))
roots: set[str] = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
roots.add(alias.name)
elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module:
roots.add(node.module)
return roots
def test_core_package_has_source_files(repo_root: Path) -> None:
"""走査対象が 0 件だと以降のテストが空振りするため前提を確認する."""
assert _core_source_files(repo_root), "core/ に Python ファイルが無い"
def test_core_does_not_import_gui_toolkit(repo_root: Path) -> None:
"""静的解析: core/ 配下のどのファイルも GUI ツールキットを import しない."""
violations: list[tuple[str, str]] = []
for source_path in _core_source_files(repo_root):
for imported in _imported_roots(source_path):
if imported.split(".")[0] in FORBIDDEN_ROOTS:
violations.append((source_path.name, imported))
assert violations == []
def test_core_does_not_import_project_gui_package(repo_root: Path) -> None:
"""静的解析: core/ が `tiasjudge.gui` に依存しない."""
violations: list[tuple[str, str]] = []
for source_path in _core_source_files(repo_root):
for imported in _imported_roots(source_path):
if imported.startswith("tiasjudge.gui") or imported == "gui":
violations.append((source_path.name, imported))
assert violations == []
def test_importing_core_does_not_load_gui_toolkit(repo_root: Path) -> None:
"""実行時: `tiasjudge.core` を import しても GUI ツールキットがロードされない."""
code = (
"import sys\n"
"import tiasjudge.core\n"
"loaded = sorted(m for m in sys.modules if m.split('.')[0] in "
f"{sorted(FORBIDDEN_ROOTS)!r})\n"
"print(loaded)\n"
)
result = subprocess.run(
[sys.executable, "-c", code],
cwd=repo_root,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
assert result.returncode == 0, result.stderr
assert result.stdout.strip() == "[]"