Files
ytstream/tests/test_runner.py
T
Claude 22d8828080 Never serve a partial file: it makes Jellyfin transcode
The 12s first-byte grace was the wrong trade and a real play found it
within the hour. A 2-hour upload took 3 minutes to start, played 6
seconds, and stalled. Jellyfin had run ffmpeg with -probesize 1G against
the growing stream and then transcoded to HLS with libx264.

The cause is the container. A fragmented MP4 with empty_moov has no
duration in its header, so the only way to get one is to sum every
fragment -- probing a growing file reads all of it. Jellyfin cannot
establish duration, codec or bitrate, so it abandons direct play and
transcodes a stream it also cannot seek. It was targeting 4.83 Mbps
against a source measured at 3.29: re-encoding a stream that already fit,
because it could not measure it.

The same video once complete reports SupportsDirectPlay with the exact
runtime and bitrate.

So FIRST_BYTE_GRACE defaults to infinite again, with --wait-timeout raised
to 600s for a 2-hour upload. A cold long video is slow to start, which is
accepted: the fetch outlives the request so a retry is instant, and a
retryable stall beats a transcode that wastes a gigabyte and cannot work.
Both failure modes are recorded at the constant in the order measured so
the 12s cap is not reintroduced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 11:32:12 +01:00

267 lines
9.7 KiB
Python

"""Orchestration: ordering, the lock, and what turns the cron check red."""
from __future__ import annotations
import pytest
from ytstream import config, discovery, jellyfin, reap, runner, subsync, videos
from conftest import add_video
@pytest.fixture()
def quiet_jellyfin(monkeypatch):
"""Jellyfin refresh is best effort; record calls instead of making them."""
calls = []
monkeypatch.setattr(jellyfin.Jellyfin, "refresh", lambda self: calls.append(1))
return calls
@pytest.fixture()
def no_thumbs(monkeypatch):
from ytstream import strm
monkeypatch.setattr(strm, "fetch_thumbnail", lambda *a, **k: False)
# ----------------------------------------------------------------------- lock
def test_lock_is_exclusive(tmp_path):
path = tmp_path / "run.lock"
with runner.exclusive_lock(path):
with pytest.raises(runner.AlreadyRunning):
with runner.exclusive_lock(path):
pass # pragma: no cover
def test_lock_is_released_after_use(tmp_path):
path = tmp_path / "run.lock"
with runner.exclusive_lock(path):
pass
with runner.exclusive_lock(path):
pass
def test_lock_is_released_even_when_the_body_raises(tmp_path):
path = tmp_path / "run.lock"
with pytest.raises(ValueError):
with runner.exclusive_lock(path):
raise ValueError("boom")
with runner.exclusive_lock(path):
pass
# --------------------------------------------------------------- materialising
def test_materialise_all_writes_everything_listed(conn, settings, media_root,
channel, no_thumbs):
for index in range(3):
add_video(conn, channel["id"], f"vid{index:08d}")
stats = runner.materialise_all(conn, settings)
assert stats["materialised"] == 3
assert stats["errors"] == 0
assert videos.queue_depth(conn) == 0
def test_tvshow_nfo_is_written_once_per_channel(conn, settings, media_root,
channel, no_thumbs):
for index in range(3):
add_video(conn, channel["id"], f"vid{index:08d}")
stats = runner.materialise_all(conn, settings)
assert stats["shows"] == 1
assert (media_root / "clabretro" / "tvshow.nfo").exists()
def test_channel_with_nothing_in_the_window_creates_no_directory(
conn, settings, media_root, channel, no_thumbs
):
"""52 of 117 measured channels are in this state, and an empty series in
Jellyfin looks like a bug rather than a quiet channel."""
add_video(conn, channel["id"], "vid00000001", state=videos.SKIPPED_OLD)
runner.materialise_all(conn, settings)
assert not (media_root / "clabretro").exists()
def test_materialise_all_honours_the_limit(conn, settings, media_root, channel,
no_thumbs):
for index in range(5):
add_video(conn, channel["id"], f"vid{index:08d}")
stats = runner.materialise_all(conn, settings, limit=2)
assert stats["materialised"] == 2
def test_materialise_skips_a_video_whose_channel_vanished(conn, settings,
media_root, channel,
no_thumbs):
add_video(conn, channel["id"], "vid00000001")
# Simulate the channel being unsubscribed between claiming and writing, with
# foreign keys off so the row survives to be found.
conn.execute("PRAGMA foreign_keys = OFF")
with conn:
conn.execute("DELETE FROM channel WHERE id = ?", (channel["id"],))
stats = runner.materialise_all(conn, settings)
assert stats["materialised"] == 0
assert stats["errors"] == 0
# ------------------------------------------------------------------ full cycle
def test_run_order_is_sync_poll_materialise_reap(conn, settings, media_root,
channel, monkeypatch,
quiet_jellyfin):
order = []
monkeypatch.setattr(subsync, "sync_all",
lambda *a: order.append("sync") or {"added": 0, "queued": 0,
"removed": 0, "refused": 0,
"sources": 0,
"pending_removal": 0})
monkeypatch.setattr(discovery, "poll_all",
lambda *a: order.append("poll") or {"queued": 0})
monkeypatch.setattr(runner, "materialise_all",
lambda *a, **k: order.append("materialise") or
{"materialised": 0, "shows": 0, "errors": 0})
monkeypatch.setattr(reap, "run",
lambda *a: order.append("reap") or {"aged_out": 0})
runner.run(conn, settings)
assert order == ["sync", "poll", "materialise", "reap"]
def test_single_channel_run_skips_the_sync(conn, settings, channel, monkeypatch,
quiet_jellyfin):
"""A targeted run is an operator action, not a mirror pass."""
monkeypatch.setattr(subsync, "sync_all",
lambda *a: pytest.fail("sync should not run"))
monkeypatch.setattr(discovery, "poll_all", lambda *a: {"queued": 0})
monkeypatch.setattr(reap, "run", lambda *a: {"aged_out": 0})
result = runner.run(conn, settings, channel_pk=channel["id"])
assert "sync" not in result
def test_run_records_last_run_at(conn, settings, channel, monkeypatch,
quiet_jellyfin):
monkeypatch.setattr(subsync, "sync_all", lambda *a: {"added": 0, "queued": 0,
"removed": 0, "refused": 0,
"sources": 0,
"pending_removal": 0})
monkeypatch.setattr(discovery, "poll_all", lambda *a: {"queued": 0})
monkeypatch.setattr(reap, "run", lambda *a: {"aged_out": 0})
runner.run(conn, settings)
assert settings.raw("last_run_at")
def test_jellyfin_is_refreshed_only_when_the_tree_changed(
conn, settings, media_root, channel, monkeypatch, quiet_jellyfin, no_thumbs
):
monkeypatch.setattr(subsync, "sync_all", lambda *a: {"added": 0, "queued": 0,
"removed": 0, "refused": 0,
"sources": 0,
"pending_removal": 0})
monkeypatch.setattr(discovery, "poll_all", lambda *a: {"queued": 0})
runner.run(conn, settings)
assert quiet_jellyfin == []
add_video(conn, channel["id"], "vid00000001")
runner.run(conn, settings)
assert len(quiet_jellyfin) == 1
# ------------------------------------------------------------------ exit codes
def test_refused_sync_turns_the_check_red():
"""A silently-broken mirror is the worst outcome available: nothing looks
wrong until someone asks why a channel never appeared."""
assert runner.exit_code({"sync": {"refused": 1}}) == 1
def test_materialise_errors_turn_the_check_red():
assert runner.exit_code({"materialise": {"errors": 2}}) == 1
def test_poll_failures_alone_do_not_turn_the_check_red():
"""Two of 119 measured channels fail permanently — terminated or private."""
assert runner.exit_code({"poll": {"failed": 2}}) == 0
def test_clean_run_is_zero():
assert runner.exit_code({"sync": {"refused": 0}, "poll": {"failed": 0},
"materialise": {"errors": 0}}) == 0
# ------------------------------------------------------------------- summarise
def test_summarise_mentions_a_refusal():
text = runner.summarise({
"sync": {"added": 0, "queued": 0, "removed": 0, "refused": 1},
"poll": {"channels": 5, "queued": 0},
"materialise": {"materialised": 0},
"reap": {"aged_out": 0},
})
assert "SYNC_REFUSED" in text
def test_summarise_is_a_single_line():
text = runner.summarise({
"sync": {"added": 1, "queued": 2, "removed": 3, "refused": 0},
"poll": {"channels": 119, "queued": 4, "shorts": 5, "live": 6, "failed": 2},
"materialise": {"materialised": 7, "errors": 0},
"reap": {"aged_out": 8},
})
assert "\n" not in text
assert "channels=119" in text
assert "materialised=7" in text
assert "aged_out=8" in text
def test_run_prunes_channels_that_have_nothing_to_show(conn, settings, media_root,
monkeypatch):
"""End to end: a subscribed channel whose directory exists only because
subscribe() wrote tvshow.nfo must not survive a run as an empty series."""
from conftest import add_channel
from ytstream import runner
empty = add_channel(conn, "UC" + "s" * 22, "Dead Channel", "dead-channel")
tree = media_root / "dead-channel"
tree.mkdir()
(tree / "tvshow.nfo").write_text("<tvshow/>")
assert runner.prune_empty_channels(conn) == 1
assert not tree.exists()
def test_run_keeps_a_channel_with_a_materialised_video(conn, settings, media_root):
from conftest import add_channel, add_video
from ytstream import runner, videos
row = add_channel(conn, "UC" + "t" * 22, "Alive", "alive")
tree = media_root / "alive"
(tree / "Season 2026").mkdir(parents=True)
(tree / "Season 2026" / "ep.strm").write_text("url")
video = add_video(conn, row["id"], "vid00000009")
videos.mark_materialised(conn, "vid00000009",
rel_path="alive/Season 2026/ep.strm", season=2026,
episode=1, upload_date="2026-08-01", duration=900,
title="t")
assert runner.prune_empty_channels(conn) == 0
assert tree.exists()