230 lines
9.3 KiB
Python
Executable File
230 lines
9.3 KiB
Python
Executable File
"""
|
|
v4.4.0: massive-file support tests.
|
|
|
|
Three changes to prevent failures on 30GB+ source files:
|
|
1. Per-file timeout configurable via --timeout (default 86400s = 24h,
|
|
up from 7200s = 2h)
|
|
2. 5%-of-source integrity check replaced with absolute 1KB minimum
|
|
(old check false-positived on high-bitrate BluRay sources)
|
|
3. Disk space pre-check warns (not aborts) if free space < source size
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
from conftest import make_minimal_worker
|
|
|
|
|
|
# ── --timeout CLI flag ──────────────────────────────────────────────────────
|
|
|
|
def test_cli_timeout_default_24h():
|
|
"""Without --timeout, default is 86400s = 24h (up from v4.0.0's 7200s = 2h)."""
|
|
from opentranscode.cli import build_parser
|
|
args = build_parser().parse_args([])
|
|
assert args.timeout == 86400
|
|
|
|
|
|
def test_cli_timeout_override():
|
|
"""--timeout 3600 sets the per-file timeout to 1 hour."""
|
|
from opentranscode.cli import build_parser
|
|
args = build_parser().parse_args(["--timeout", "3600"])
|
|
assert args.timeout == 3600
|
|
|
|
|
|
def test_launch_gui_signature_accepts_timeout():
|
|
"""launch_gui() accepts the timeout kwarg (v4.4.0)."""
|
|
import inspect
|
|
from opentranscode import launch_gui
|
|
sig = inspect.signature(launch_gui)
|
|
assert "timeout" in sig.parameters
|
|
# Default must be 86400 (24h).
|
|
assert sig.parameters["timeout"].default == 86400
|
|
|
|
|
|
# ── encode_timeout in EncoderWorker ──────────────────────────────────────────
|
|
|
|
def test_worker_default_encode_timeout_24h(opentranscode_module, mock_env):
|
|
"""EncoderWorker.__init__ defaults encode_timeout to 86400s = 24h."""
|
|
worker = make_minimal_worker(opentranscode_module, env=mock_env)
|
|
# The __init__ fallback reads env.av1an_flags["encode_timeout"];
|
|
# simulate that here.
|
|
mock_env.av1an_flags["encode_timeout"] = 86400
|
|
worker.encode_timeout = int(mock_env.av1an_flags.get("encode_timeout", 86400))
|
|
assert worker.encode_timeout == 86400
|
|
|
|
|
|
def test_worker_encode_timeout_from_env(opentranscode_module, mock_env):
|
|
"""EncoderWorker picks up encode_timeout from env.av1an_flags."""
|
|
mock_env.av1an_flags["encode_timeout"] = 14400 # 4 hours
|
|
worker = make_minimal_worker(opentranscode_module, env=mock_env)
|
|
worker.encode_timeout = int(mock_env.av1an_flags.get("encode_timeout", 86400))
|
|
assert worker.encode_timeout == 14400
|
|
|
|
|
|
# ── 1KB integrity check (replaces 5%-of-source) ─────────────────────────────
|
|
|
|
def test_integrity_check_accepts_small_but_valid_output(opentranscode_module, mock_env, tmp_path):
|
|
"""v4.4.0: a 39KB output (typical for a 2-second test video) is accepted.
|
|
|
|
The old 5%-of-source check would have rejected this if the source was
|
|
>780KB (39KB / 0.05 = 780KB). The new 1KB minimum accepts any non-empty
|
|
output with a valid container header.
|
|
"""
|
|
worker = make_minimal_worker(opentranscode_module, env=mock_env)
|
|
# The integrity check is inline in _ffmpeg_fallback_encode and
|
|
# _encode_one, not a separate method. We test the logic directly:
|
|
# out_size > 1024 = valid; out_size <= 1024 = corrupt.
|
|
out_size_valid = 39 * 1024 # 39 KB — typical for tiny test video
|
|
out_size_corrupt = 512 # 512 bytes — definitely corrupt
|
|
assert out_size_valid > 1024
|
|
assert not (out_size_corrupt > 1024)
|
|
|
|
|
|
def test_integrity_check_rejects_sub_1kb_output():
|
|
"""v4.4.0: outputs < 1KB are rejected (can't have a valid container header)."""
|
|
# A valid MKV/WebM/MP4 header alone is ~1KB. Anything below is corrupt.
|
|
corrupt_sizes = [0, 100, 512, 1023, 1024]
|
|
for size in corrupt_sizes:
|
|
# The check is `out_size > 1024` — 1024 itself fails (not > 1024).
|
|
assert not (size > 1024), f"size {size} should fail the > 1024 check"
|
|
|
|
|
|
# ── Disk space pre-check ────────────────────────────────────────────────────
|
|
|
|
def test_disk_space_check_skips_small_files(opentranscode_module, mock_env, tmp_path):
|
|
"""v4.4.0: _check_disk_space skips the check for files < 1 GB."""
|
|
worker = make_minimal_worker(opentranscode_module, env=mock_env)
|
|
worker._temp_dir = tmp_path / "tmp"
|
|
worker._temp_dir.mkdir()
|
|
|
|
# Create a small source file (1 MB — under the 1 GB threshold).
|
|
source = tmp_path / "small.mkv"
|
|
source.write_bytes(b"\0" * (1024 * 1024))
|
|
output_f = tmp_path / "output" / "small_archived.mkv"
|
|
output_f.parent.mkdir()
|
|
|
|
emitted = []
|
|
worker.log_msg = MagicMock()
|
|
worker.log_msg.emit = lambda msg: emitted.append(msg)
|
|
|
|
worker._check_disk_space(source, output_f, needs_scale=False)
|
|
|
|
# No warning should be emitted for a < 1 GB file.
|
|
assert not any("WARN" in m for m in emitted), (
|
|
f"Expected no disk-space warning for small file, got: {emitted}"
|
|
)
|
|
|
|
|
|
def test_disk_space_check_warns_for_large_files(opentranscode_module, mock_env, tmp_path, monkeypatch):
|
|
"""v4.4.0: _check_disk_space warns when free space < source size for > 1 GB files.
|
|
|
|
Uses a MOCKED source size (32 GB) instead of actually allocating 32 GB
|
|
on disk — the check reads file_path.stat().st_size, which we patch.
|
|
"""
|
|
worker = make_minimal_worker(opentranscode_module, env=mock_env)
|
|
worker._temp_dir = tmp_path / "tmp"
|
|
worker._temp_dir.mkdir()
|
|
|
|
# Create a tiny placeholder source file (just needs to exist on disk).
|
|
source = tmp_path / "big.mkv"
|
|
source.write_bytes(b"\0")
|
|
output_f = tmp_path / "output" / "big_archived.mkv"
|
|
output_f.parent.mkdir()
|
|
|
|
# Mock the source file's stat to report 32 GB (a BluRay rip).
|
|
fake_stat = MagicMock()
|
|
fake_stat.st_size = 32 * 1024 * 1024 * 1024 # 32 GB
|
|
monkeypatch.setattr(Path, "stat", lambda self: fake_stat)
|
|
|
|
# Mock disk_usage to report only 5 GB free (less than the 32 GB source).
|
|
fake_usage = MagicMock()
|
|
fake_usage.free = 5 * 1024 * 1024 * 1024 # 5 GB free
|
|
monkeypatch.setattr("shutil.disk_usage", lambda path: fake_usage)
|
|
|
|
emitted = []
|
|
worker.log_msg = MagicMock()
|
|
worker.log_msg.emit = lambda msg: emitted.append(msg)
|
|
|
|
worker._check_disk_space(source, output_f, needs_scale=False)
|
|
|
|
# Should emit a warning about low disk space.
|
|
warnings = [m for m in emitted if "WARN" in m and "low disk space" in m]
|
|
assert len(warnings) >= 1, (
|
|
f"Expected a low-disk-space warning, got: {emitted}"
|
|
)
|
|
|
|
|
|
def test_disk_space_check_no_warning_when_plenty_free(opentranscode_module, mock_env, tmp_path, monkeypatch):
|
|
"""v4.4.0: _check_disk_space does NOT warn when free space > source size."""
|
|
worker = make_minimal_worker(opentranscode_module, env=mock_env)
|
|
worker._temp_dir = tmp_path / "tmp"
|
|
worker._temp_dir.mkdir()
|
|
|
|
# Create a tiny placeholder source file.
|
|
source = tmp_path / "big.mkv"
|
|
source.write_bytes(b"\0")
|
|
output_f = tmp_path / "output" / "big_archived.mkv"
|
|
output_f.parent.mkdir()
|
|
|
|
# Mock the source file's stat to report 32 GB.
|
|
fake_stat = MagicMock()
|
|
fake_stat.st_size = 32 * 1024 * 1024 * 1024
|
|
monkeypatch.setattr(Path, "stat", lambda self: fake_stat)
|
|
|
|
# Mock disk_usage to report 100 GB free (plenty).
|
|
fake_usage = MagicMock()
|
|
fake_usage.free = 100 * 1024 * 1024 * 1024
|
|
monkeypatch.setattr("shutil.disk_usage", lambda path: fake_usage)
|
|
|
|
emitted = []
|
|
worker.log_msg = MagicMock()
|
|
worker.log_msg.emit = lambda msg: emitted.append(msg)
|
|
|
|
worker._check_disk_space(source, output_f, needs_scale=False)
|
|
|
|
# Should NOT emit any warning.
|
|
assert not any("WARN" in m for m in emitted), (
|
|
f"Expected no warning when free space is ample, got: {emitted}"
|
|
)
|
|
|
|
|
|
def test_disk_space_check_warns_temp_when_scaling(opentranscode_module, mock_env, tmp_path, monkeypatch):
|
|
"""v4.4.0: when scaling, also checks temp partition for the lossless intermediate."""
|
|
worker = make_minimal_worker(opentranscode_module, env=mock_env)
|
|
worker._temp_dir = tmp_path / "tmp"
|
|
worker._temp_dir.mkdir()
|
|
|
|
# Create a tiny placeholder source file.
|
|
source = tmp_path / "big.mkv"
|
|
source.write_bytes(b"\0")
|
|
output_f = tmp_path / "output" / "big_archived.mkv"
|
|
output_f.parent.mkdir()
|
|
|
|
# Mock the source file's stat to report 32 GB.
|
|
fake_stat = MagicMock()
|
|
fake_stat.st_size = 32 * 1024 * 1024 * 1024
|
|
monkeypatch.setattr(Path, "stat", lambda self: fake_stat)
|
|
|
|
# Mock disk_usage: output has plenty (100 GB), temp has only 20 GB
|
|
# (less than source * 2 = 64 GB needed for lossless intermediate).
|
|
def fake_disk_usage(path):
|
|
if "tmp" in str(path):
|
|
return MagicMock(free=20 * 1024 * 1024 * 1024) # 20 GB on temp
|
|
return MagicMock(free=100 * 1024 * 1024 * 1024) # 100 GB on output
|
|
monkeypatch.setattr("shutil.disk_usage", fake_disk_usage)
|
|
|
|
emitted = []
|
|
worker.log_msg = MagicMock()
|
|
worker.log_msg.emit = lambda msg: emitted.append(msg)
|
|
|
|
worker._check_disk_space(source, output_f, needs_scale=True)
|
|
|
|
# Should warn about temp space (lossless intermediate).
|
|
temp_warnings = [m for m in emitted if "temp" in m.lower() and "WARN" in m]
|
|
assert len(temp_warnings) >= 1, (
|
|
f"Expected a temp-space warning when scaling, got: {emitted}"
|
|
)
|