"""visualize/viewer.py の仕様ベーステスト.
仕様出典:
- Phase 1 実装サマリー(show_scene 仕様)
- docs/01_GUIDE/GUIDE_08_テスト方針.md(可視化テストは smoke テスト)
テスト方針:
- PyVista は必ず off_screen=True で実行する(GUI を開かない)
- 「ファイルが生成されたか」程度の smoke テストで十分(GUIDE_08)
- Mitsuba をモックしない(GUIDE_08)
テスト対象:
show_scene(scene_config, project_root, irradiance=None, *, off_screen=False, screenshot=None)
"""
from __future__ import annotations
from pathlib import Path
import numpy as np
import pytest
from sm_tias_lightsim.scene.schema import SceneConfig
from sm_tias_lightsim.visualize.baker import bake_irradiance
from sm_tias_lightsim.visualize.viewer import show_scene
# ---------------------------------------------------------------------------
# フィクスチャ:minimal.yaml をそのまま使う
# ---------------------------------------------------------------------------
MINIMAL_YAML = Path(__file__).parent.parent / "fixtures" / "minimal.yaml"
@pytest.fixture(scope="module")
def minimal_scene_config() -> tuple[SceneConfig, Path]:
"""tests/fixtures/minimal.yaml から SceneConfig と project_root を返す.
module スコープで 1 回だけロード(YAML パース・Pydantic 検証は速いため).
"""
from sm_tias_lightsim.scene.schema import load_scene_yaml
config = load_scene_yaml(MINIMAL_YAML)
project_root = MINIMAL_YAML.parent
return config, project_root
# ---------------------------------------------------------------------------
# 1. irradiance なしの smoke テスト
# ---------------------------------------------------------------------------
@pytest.mark.slow
def test_show_scene_without_irradiance_creates_screenshot(
tmp_path: Path,
minimal_scene_config: tuple[SceneConfig, Path],
):
"""irradiance=None のとき screenshot が生成されること(no-bake モード).
仕様: irradiance なし → light gray の半透明メッシュで表示
screenshot 指定時は off_screen=True を強制し,screenshot を保存する
"""
config, project_root = minimal_scene_config
screenshot_path = tmp_path / "out_no_irr.png"
show_scene(
config,
project_root,
irradiance=None,
screenshot=screenshot_path,
)
assert screenshot_path.exists(), "irradiance なしで screenshot ファイルが生成されること"
assert screenshot_path.stat().st_size > 0, "screenshot ファイルが空でないこと"
# ---------------------------------------------------------------------------
# 2. irradiance ありの smoke テスト
# ---------------------------------------------------------------------------
@pytest.mark.slow
def test_show_scene_with_irradiance_creates_screenshot(
tmp_path: Path,
minimal_scene_config: tuple[SceneConfig, Path],
):
"""irradiance を渡したとき screenshot が生成されること(bake モード).
仕様: irradiance あり → 各メッシュを cell_data["irradiance"] で着色(viridis)
"""
config, project_root = minimal_scene_config
# ベイク結果を取得
irradiance = bake_irradiance(config, project_root)
screenshot_path = tmp_path / "out_with_irr.png"
show_scene(
config,
project_root,
irradiance=irradiance,
screenshot=screenshot_path,
)
assert screenshot_path.exists(), "irradiance ありで screenshot ファイルが生成されること"
assert screenshot_path.stat().st_size > 0, "screenshot ファイルが空でないこと"
# ---------------------------------------------------------------------------
# 3. screenshot=None, off_screen=True は no-op であること(例外を投げない)
# ---------------------------------------------------------------------------
@pytest.mark.slow
def test_show_scene_off_screen_without_screenshot_is_noop(
tmp_path: Path,
minimal_scene_config: tuple[SceneConfig, Path],
):
"""screenshot=None かつ off_screen=True のとき例外を投げないこと(no-op).
仕様: screenshot=None かつ off_screen=True のときは no-op
(viewer.py show_scene の末尾条件分岐)
"""
config, project_root = minimal_scene_config
# 例外が発生しないこと(戻り値は None)
result = show_scene(
config,
project_root,
irradiance=None,
off_screen=True,
screenshot=None,
)
assert result is None, "show_scene は None を返すこと"
# ---------------------------------------------------------------------------
# 4. screenshot 指定時に off_screen が強制されること(例外なく完了する)
# ---------------------------------------------------------------------------
@pytest.mark.slow
def test_show_scene_screenshot_forces_off_screen(
tmp_path: Path,
minimal_scene_config: tuple[SceneConfig, Path],
):
"""screenshot を指定すると off_screen=False でも off_screen=True に強制されること.
仕様: screenshot 指定時は off_screen=True を強制し,screenshot を保存する
GUI ウィンドウが開かないことを間接的に確認する(off_screen 強制は CI/CD 環境で必須).
"""
config, project_root = minimal_scene_config
screenshot_path = tmp_path / "forced_offscreen.png"
# off_screen=False でも screenshot 指定なら強制的に off_screen になるはず
show_scene(
config,
project_root,
irradiance=None,
off_screen=False, # これは screenshot 指定で上書きされる
screenshot=screenshot_path,
)
assert screenshot_path.exists(), "off_screen=False でも screenshot 指定なら保存されること"
# ---------------------------------------------------------------------------
# 5. irradiance の cell_data がメッシュに紐付けられていること(smoke)
# ---------------------------------------------------------------------------
@pytest.mark.slow
def test_show_scene_irradiance_with_wrong_mesh_id_does_not_crash(
tmp_path: Path,
minimal_scene_config: tuple[SceneConfig, Path],
):
"""存在しない mesh_id のキーを irradiance に渡しても例外を投げないこと.
仕様: irradiance が None でないが mesh_id が辞書に含まれない場合,
そのメッシュは light gray の半透明で表示される(else ブランチ).
"""
config, project_root = minimal_scene_config
screenshot_path = tmp_path / "wrong_id.png"
# 存在しない mesh_id を持つ irradiance を渡す
fake_irradiance = {"nonexistent_mesh": np.zeros(10, dtype=np.float32)}
show_scene(
config,
project_root,
irradiance=fake_irradiance,
screenshot=screenshot_path,
)
assert screenshot_path.exists(), "不正な mesh_id でも例外を投げず screenshot が生成されること"