Zum Inhalt

pipeline/tests/test_progress.py

# SPDX-License-Identifier: AGPL-3.0-or-later

"""Tests for the progress.json single-source-of-truth helpers."""

from __future__ import annotations

from pathlib import Path

from autosplat.progress import ProgressState, read_progress, write_progress


def test_write_then_read_round_trips(tmp_path: Path) -> None:
    state = ProgressState(
        stage="train",
        elapsed_s=1204.0,
        est_pct=0.5017,
        eta_s=2400.0,
        updated_at="2026-05-29T09:29:52Z",
        step=12000,
        total_steps=30000,
        psnr=24.8,
    )
    write_progress(tmp_path, state)

    loaded = read_progress(tmp_path)
    assert loaded == state


def test_write_creates_progress_json_at_capture_root(tmp_path: Path) -> None:
    state = ProgressState(
        stage="train",
        elapsed_s=2.0,
        est_pct=0.0,
        eta_s=2400.0,
        updated_at="2026-05-29T09:09:50Z",
    )
    write_progress(tmp_path, state)
    assert (tmp_path / "progress.json").is_file()


def test_optional_metric_fields_default_to_none(tmp_path: Path) -> None:
    """A time-only heartbeat (plateau disabled) carries no step/psnr."""
    state = ProgressState(
        stage="train",
        elapsed_s=10.0,
        est_pct=0.004,
        eta_s=2400.0,
        updated_at="2026-05-29T09:10:00Z",
    )
    write_progress(tmp_path, state)
    loaded = read_progress(tmp_path)
    assert loaded is not None
    assert loaded.step is None
    assert loaded.total_steps is None
    assert loaded.psnr is None


def test_read_missing_file_returns_none(tmp_path: Path) -> None:
    assert read_progress(tmp_path) is None


def test_read_corrupt_json_returns_none(tmp_path: Path) -> None:
    (tmp_path / "progress.json").write_text("{not valid json")
    assert read_progress(tmp_path) is None


def test_write_is_atomic_no_leftover_tmp(tmp_path: Path) -> None:
    """Atomic write leaves only progress.json behind — no .tmp dross that a
    concurrent reader could trip over."""
    state = ProgressState(
        stage="train",
        elapsed_s=1.0,
        est_pct=0.0,
        eta_s=100.0,
        updated_at="2026-05-29T09:00:00Z",
    )
    write_progress(tmp_path, state)
    leftovers = [p.name for p in tmp_path.iterdir() if p.name != "progress.json"]
    assert leftovers == []


def test_write_overwrites_previous_state(tmp_path: Path) -> None:
    first = ProgressState("train", 1.0, 0.0, 100.0, "2026-05-29T09:00:00Z")
    second = ProgressState("train", 50.0, 0.5, 100.0, "2026-05-29T09:00:50Z")
    write_progress(tmp_path, first)
    write_progress(tmp_path, second)
    assert read_progress(tmp_path) == second