Zum Inhalt

tests/test_runner.py

"""Tests for webgui.runner — JobRegistry + subprocess spawning."""

import sys
from pathlib import Path

import pytest


def test_registry_starts_empty():
    from webgui.runner import JobRegistry

    reg = JobRegistry()
    assert reg.current is None


def test_registry_claim_succeeds_when_empty():
    from webgui.runner import ActiveJob, JobRegistry

    reg = JobRegistry()
    job = ActiveJob(
        stem="folge-082",
        audio_path=Path("/tmp/a.m4a"),
        output_dir=Path("/tmp/out"),
        process=None,
        log_file=Path("/tmp/log"),
        kind="pipeline",
    )
    claimed = reg.try_claim(job)
    assert claimed is True
    assert reg.current is job
    assert reg.current.stem == "folge-082"


def test_registry_claim_fails_when_busy():
    from webgui.runner import ActiveJob, JobRegistry

    reg = JobRegistry()
    first = ActiveJob(
        stem="first",
        audio_path=Path("/a"),
        output_dir=Path("/b"),
        process=None,
        log_file=Path("/c"),
        kind="pipeline",
    )
    second = ActiveJob(
        stem="second",
        audio_path=Path("/a"),
        output_dir=Path("/b"),
        process=None,
        log_file=Path("/c"),
        kind="pipeline",
    )
    reg.try_claim(first)
    assert reg.try_claim(second) is False
    assert reg.current.stem == "first"


def test_registry_release_frees_slot():
    from webgui.runner import ActiveJob, JobRegistry

    reg = JobRegistry()
    job = ActiveJob(
        stem="x",
        audio_path=Path("/a"),
        output_dir=Path("/b"),
        process=None,
        log_file=Path("/c"),
        kind="pipeline",
    )
    reg.try_claim(job)
    reg.release(job)
    assert reg.current is None


def test_registry_release_ignores_wrong_job():
    """Releasing a job that doesn't match current should not free the slot."""
    from webgui.runner import ActiveJob, JobRegistry

    reg = JobRegistry()
    real = ActiveJob(
        stem="real",
        audio_path=Path("/a"),
        output_dir=Path("/b"),
        process=None,
        log_file=Path("/c"),
        kind="pipeline",
    )
    other = ActiveJob(
        stem="other",
        audio_path=Path("/a"),
        output_dir=Path("/b"),
        process=None,
        log_file=Path("/c"),
        kind="pipeline",
    )
    reg.try_claim(real)
    reg.release(other)
    assert reg.current is real


def test_spawn_pipeline_writes_to_logfile_and_releases_slot(tmp_path):
    """End-to-end: spawn mock pipeline, verify log file populated + slot freed after exit."""
    import time

    from webgui.runner import JobRegistry, spawn_pipeline

    reg = JobRegistry()
    output_dir = tmp_path / "stem-x"
    output_dir.mkdir()
    log_file = output_dir / "run-test.log"

    mock_cmd = [sys.executable, str(Path("tests/fixtures/mock_pipeline.py"))]
    job = spawn_pipeline(
        cmd=mock_cmd,
        stem="stem-x",
        audio_path=Path("/tmp/a.m4a"),
        output_dir=output_dir,
        log_file=log_file,
        registry=reg,
    )

    job.process.wait(timeout=10)

    # Wait briefly for reader thread to finish + release
    for _ in range(30):
        if reg.current is None:
            break
        time.sleep(0.1)

    log_contents = log_file.read_text()
    assert "SCHRITT 1" in log_contents
    assert "Rendering 100.0%" in log_contents
    assert reg.current is None


def test_spawn_pipeline_fails_when_slot_busy(tmp_path):
    from webgui.runner import ActiveJob, JobRegistry, spawn_pipeline

    reg = JobRegistry()
    existing = ActiveJob(
        stem="busy",
        audio_path=Path("/a"),
        output_dir=Path("/b"),
        process=None,
        log_file=Path("/c"),
        kind="pipeline",
    )
    reg.try_claim(existing)

    with pytest.raises(RuntimeError) as exc:
        spawn_pipeline(
            cmd=[sys.executable, "-c", "pass"],
            stem="new",
            audio_path=Path("/tmp/a.m4a"),
            output_dir=tmp_path,
            log_file=tmp_path / "x.log",
            registry=reg,
        )
    assert "busy" in str(exc.value).lower() or "slot" in str(exc.value).lower()


def test_spawn_pipeline_releases_slot_when_popen_fails(tmp_path):
    """Critical: if Popen raises (bad binary, OS error), the slot must NOT
    stay claimed — otherwise the singleton registry becomes permanently
    unusable. Regression guard for T6/T7 code-quality review finding."""
    from webgui.runner import JobRegistry, spawn_pipeline

    reg = JobRegistry()
    with pytest.raises(FileNotFoundError):
        spawn_pipeline(
            cmd=["/nonexistent/binary/that/cannot/be/found-xyz"],
            stem="failed-spawn",
            audio_path=Path("/tmp/a.m4a"),
            output_dir=tmp_path / "out",
            log_file=tmp_path / "out" / "x.log",
            registry=reg,
        )
    # Slot must be free even though Popen blew up
    assert reg.current is None