Newer
Older
SmTIAS-LightSim / tests / cli / test_evaluate_no_figures.py
"""sm-evaluate の --no-figures オプションの仕様ベーステスト(統合テスト).

仕様出典:
- docs/04_SPEC/SPEC_04_評価仕様.md「出力ファイル仕様」(既定は metrics.csv + figures/ 4 種)
- docs/02_ENV/ENV_04_開発コマンド.md(sm-evaluate は PNG / luminance.npy を評価し
  metrics.csv + figures/ を出力する)
- 実装サマリー: --no-figures を付けると figures/ を作らず描画もせず,
  サマリーの figures/ 行も出さない(指標だけ欲しいスイープ用)

検証する仕様:
- --no-figures なしは従来どおり(後方互換): metrics.csv + figures/ 4 種
- --no-figures ありは metrics.csv のみ.終了コードは 0
- どちらでも metrics.csv の内容は同一(描画は指標に影響しない)

テスト方針(testing.md): subprocess ではなく main() を直接呼ぶ.tmp_path で実ファイルを使う.
"""

from __future__ import annotations

import matplotlib  # noqa: I001

# headless 環境でも動くよう Agg バックエンドを pyplot より前に設定する
matplotlib.use("Agg")

from pathlib import Path

import numpy
import pytest

from sm_tias_lightsim.cli.evaluate import main

_FIGURE_FILES = ("luminance_map.png", "histogram.png", "radial_profile.png", "zone_map.png")


def _make_inputs(tmp_path: Path, seed: int = 0) -> tuple[Path, Path]:
    """合成輝度 .npy と評価 YAML を書き出してパスを返す(seed 固定)."""
    rng = numpy.random.RandomState(seed)
    luminance = numpy.full((128, 128), 100.0) + rng.normal(0, 5, (128, 128))
    npy_path = tmp_path / "luminance.npy"
    numpy.save(npy_path, luminance)

    config_path = tmp_path / "eval_config.yaml"
    config_path.write_text(
        """\
roi:
  type: image_pixel
  x: 16
  y: 16
  width: 96
  height: 96
zones:
  enabled: true
  boundaries: [0.33, 0.66]
metrics:
  cov: true
  std: true
  max_min: true
  radial_profile: true
""",
        encoding="utf-8",
    )
    return npy_path, config_path


def _run(npy: Path, config: Path, out_dir: Path, *extra: str) -> int:
    return main(
        ["--image", str(npy), "--config", str(config), "--output", str(out_dir), *extra]
    )


def test_no_figures_returns_0(tmp_path: Path):
    """--no-figures 付きでも正常終了(0)すること."""
    npy, config = _make_inputs(tmp_path, seed=0)
    assert _run(npy, config, tmp_path / "out", "--no-figures") == 0


def test_no_figures_creates_metrics_csv(tmp_path: Path):
    """--no-figures でも metrics.csv は出力されること."""
    npy, config = _make_inputs(tmp_path, seed=1)
    out_dir = tmp_path / "out"
    _run(npy, config, out_dir, "--no-figures")
    assert (out_dir / "metrics.csv").is_file()


def test_no_figures_does_not_create_figures_dir(tmp_path: Path):
    """--no-figures のとき figures/ ディレクトリが作られないこと."""
    npy, config = _make_inputs(tmp_path, seed=2)
    out_dir = tmp_path / "out"
    _run(npy, config, out_dir, "--no-figures")
    assert not (out_dir / "figures").exists()


def test_no_figures_writes_no_png(tmp_path: Path):
    """--no-figures のとき PNG が 1 つも出力されないこと."""
    npy, config = _make_inputs(tmp_path, seed=3)
    out_dir = tmp_path / "out"
    _run(npy, config, out_dir, "--no-figures")
    assert list(out_dir.rglob("*.png")) == []


def test_no_figures_summary_has_no_figures_line(tmp_path: Path, capsys):
    """--no-figures のとき標準出力のサマリーに figures/ 行が出ないこと."""
    npy, config = _make_inputs(tmp_path, seed=4)
    _run(npy, config, tmp_path / "out", "--no-figures")
    out = capsys.readouterr().out
    assert "=== 均一性評価サマリー ===" in out
    assert "metrics.csv" in out
    assert "figures/" not in out


def test_default_still_creates_figures(tmp_path: Path):
    """--no-figures を付けなければ従来どおり figures/ 4 種が出ること(後方互換)."""
    npy, config = _make_inputs(tmp_path, seed=5)
    out_dir = tmp_path / "out"
    assert _run(npy, config, out_dir) == 0
    for name in _FIGURE_FILES:
        assert (out_dir / "figures" / name).is_file(), name


def test_metrics_csv_identical_with_and_without_figures(tmp_path: Path):
    """--no-figures の有無で metrics.csv の内容が一致すること."""
    npy, config = _make_inputs(tmp_path, seed=6)
    with_dir = tmp_path / "with"
    without_dir = tmp_path / "without"
    _run(npy, config, with_dir)
    _run(npy, config, without_dir, "--no-figures")
    assert (with_dir / "metrics.csv").read_text(encoding="utf-8") == (
        without_dir / "metrics.csv"
    ).read_text(encoding="utf-8")


def test_help_mentions_no_figures(capsys):
    """--help に --no-figures が説明されていること."""
    with pytest.raises(SystemExit) as exc:
        main(["--help"])
    assert exc.value.code == 0
    assert "--no-figures" in capsys.readouterr().out


def test_no_figures_error_path_still_returns_1(tmp_path: Path, capsys):
    """--no-figures 付きでも異常時は「エラー: 」を stderr に出して 1 を返すこと."""
    _, config = _make_inputs(tmp_path, seed=7)
    result = _run(tmp_path / "missing.npy", config, tmp_path / "out", "--no-figures")
    assert result == 1
    assert "エラー: " in capsys.readouterr().err