Ctrl K
ComunidadGuías…

backend/tests/test_foreign_tracks_enumeration_resume.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""A foreign-track walk that is stopped must still get somewhere (Forgejo #68).

Every run started a new generation and walked from the root; the prune and
the probe phase only follow a completed walk. A walk longer than the job's
ceiling — a Raspberry Pi on an SD card, a slow network mount, a very large
library — was stopped every time and never finished.
"""

import threading

from services.foreign_tracks import sweep as sw
from services.foreign_tracks.state import load_state
from services.scheduler import cancellation
from tests.test_foreign_tracks_sweep_tick import CFG, FakeClock
from tests.test_foreign_tracks_sweep_tick import app as app  # noqa: F401 — fixture
from tests.test_foreign_tracks_sweep_tick import repo as repo  # noqa: F401 — fixture

FILES = [(f"/media/{n}.mkv", 10, 1.0) for n in "abcd"]


def _slice(tmp_path, repo, monkeypatch, *, stop_after=None):
    """One slice over FILES; asked to stop once ``stop_after`` files were yielded."""
    event = threading.Event()
    upserted = []
    real_upsert = repo.upsert_seen

    def upsert(path, *a, **k):
        upserted.append(path)
        return real_upsert(path, *a, **k)

    def walk(*a, **k):
        for n, item in enumerate(FILES, 1):
            yield item
            if stop_after is not None and n >= stop_after:
                event.set()

    monkeypatch.setattr(repo, "upsert_seen", upsert)
    monkeypatch.setattr(sw, "iter_video_files", walk)
    monkeypatch.setattr(sw, "_probe_file", lambda path: {"streams": []})
    token = cancellation.activate(event)
    try:
        sw.run_slice(str(tmp_path), CFG, budget_s=60, now_fn=FakeClock(), repo=repo)
    finally:
        cancellation.deactivate(token)
    return upserted


def test_a_stopped_walk_keeps_its_generation(app, repo, tmp_path, monkeypatch):
    _slice(tmp_path, repo, monkeypatch, stop_after=2)
    generation = load_state().generation
    _slice(tmp_path, repo, monkeypatch)

    assert load_state().generation == generation
    assert load_state().enumeration_complete


def test_the_next_walk_only_records_what_the_last_one_did_not(app, repo, tmp_path, monkeypatch):
    _slice(tmp_path, repo, monkeypatch, stop_after=2)
    upserted = _slice(tmp_path, repo, monkeypatch)

    assert upserted == ["/media/c.mkv", "/media/d.mkv"]


def test_the_finished_walk_prunes_only_files_no_part_saw(app, repo, tmp_path, monkeypatch):
    repo.upsert_seen("/media/gone.mkv", 10, 1.0, generation=0)
    _slice(tmp_path, repo, monkeypatch, stop_after=2)
    _slice(tmp_path, repo, monkeypatch)

    from db.models.foreign_tracks import ForeignTrackScan

    paths = {r.path for r in repo.session.query(ForeignTrackScan).all()}
    assert paths == {f[0] for f in FILES}


def test_rows_under_an_unreadable_folder_survive_the_prune(app, repo, tmp_path, monkeypatch):
    """os.walk skips a folder it cannot list; its files are not deleted ones."""
    repo.upsert_seen("/media/flaky/x.mkv", 10, 1.0, generation=0)
    repo.upsert_seen("/media/gone.mkv", 10, 1.0, generation=0)

    def walk(*a, unreadable=None, **k):
        unreadable.append("/media/flaky")
        yield from FILES

    monkeypatch.setattr(sw, "iter_video_files", walk)
    monkeypatch.setattr(sw, "_probe_file", lambda path: {"streams": []})
    sw.run_slice(str(tmp_path), CFG, budget_s=60, now_fn=FakeClock(), repo=repo)

    from db.models.foreign_tracks import ForeignTrackScan

    paths = {r.path for r in repo.session.query(ForeignTrackScan).all()}
    assert "/media/flaky/x.mkv" in paths
    assert "/media/gone.mkv" not in paths


def test_safe_walk_reports_folders_it_cannot_read(tmp_path):
    from services.cleanup_executors import _safe_walk

    locked = tmp_path / "locked"
    locked.mkdir()
    (locked / "a.mkv").write_bytes(b"x")
    locked.chmod(0)
    unreadable: list[str] = []
    try:
        list(_safe_walk(str(tmp_path), unreadable))
    finally:
        locked.chmod(0o755)

    assert unreadable == [str(locked)]