devedeng-extended/tests/conftest.py
Luna 691664095b Add pytest suite and CI (roadmap A1)
37 headless tests covering the logic that is cheap to break:
- split planner: plan_disc_split, chapter helpers (whole-fit, multi-disc
  partition, single-movie chapter/even-cut split, contiguous coverage)
- High-profile fill-disc bitrate math (disc count + bounds + capacity)
- ffmpeg command generation: profiles, text/image hardsub, 2-pass, nice,
  progress-to-file, even pad/crop + positive offsets, -ss seek, and the
  AC3 448k audio-clamp regression
- ffprobe subtitle detection: text/image classification, tags/disposition

tests/conftest.py makes the in-tree package importable and installs the
gettext _ builtin so modules run headless. Forgejo Actions workflow runs
the suite on push/PR. Update CLAUDE.md's testing section accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 22:26:22 -07:00

119 lines
3.6 KiB
Python

"""Shared test fixtures/helpers for the devedeng-extended test suite.
These tests exercise pure logic and command generation without launching the
GTK UI. They import the package from ``src/`` and stub out the gettext ``_``
builtin so modules that call ``_(...)`` at runtime work headless.
"""
import os
import sys
import gettext
# make the in-tree package importable without installing
_SRC = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
# ensure the gettext _() builtin exists (the app normally installs it at start)
gettext.install("devedeng")
import pytest
class FakeMovie:
"""Minimal stand-in for file_movie for the pure split/bitrate planners.
Only the attributes those functions read are provided.
"""
element_type = "file_movie"
def __init__(self, rate_kBps=586.5, length=3600, chapters_step=5,
divide=True, manual=None, audio_streams=1, subtitles=0):
self._rate = rate_kBps
self.original_length = length
self.chapter_size = chapters_step
self.divide_in_chapters = divide
self.chapter_list_entry = manual
self.audio_streams = audio_streams
self.subtitles_list = [None] * subtitles
def get_kbytes_per_second(self):
return self._rate
@pytest.fixture
def make_movie():
"""Factory for FakeMovie instances used by the planner tests."""
def _make(**kwargs):
return FakeMovie(**kwargs)
return _make
class FakeFileProject:
"""Stand-in for a file_movie passed to ffmpeg/avconv.convert_file.
Defaults describe a typical 1080p 16:9 source targeting NTSC DVD with no
hardsub. Override fields per test.
"""
def __init__(self, **overrides):
self.file_name = "/movies/input.mkv"
self.title_name = "input"
self.two_pass_encoding = False
self.volume = 100
self.audio_delay = 0.0
self.copy_sound = False
self.no_reencode_audio_video = False
self.is_mpeg_ps = False
self.video_list = [0]
self.audio_list = [1]
self.deinterlace = "deinterlace_none"
self.prerotation = 0
self.rotation = "rotation_0"
self.mirror_vertical = False
self.mirror_horizontal = False
self.original_width = 1920
self.original_height = 1080
self.width_midle = 1920
self.height_midle = 1080
self.width_final = 720
self.height_final = 480
self.original_fps = 24
self.format_pal = False
self.sound5_1 = False
self.gop12 = True
self.video_rate_final = 4500
self.audio_rate_final = 192
self.aspect_ratio_final = 1.7777777777777777
self.original_length = 7200
self.hardsub_index = -1
self.hardsub_kind = "text"
self.embedded_subtitles = []
for k, v in overrides.items():
setattr(self, k, v)
@pytest.fixture
def make_file_project():
def _make(**overrides):
return FakeFileProject(**overrides)
return _make
def ffmpeg_converter(disc_type="dvd", profile="compatibility"):
"""Build an ffmpeg converter with the config singleton set for a test."""
import devedeng.configuration_data
import devedeng.ffmpeg
cfg = devedeng.configuration_data.configuration.get_config()
cfg.disc_type = disc_type
cfg.encode_profile = profile
return devedeng.ffmpeg.ffmpeg()
def arg_after(cmd, flag):
"""Return the argument following ``flag`` in an argv list, or None."""
for i, a in enumerate(cmd):
if a == flag and i + 1 < len(cmd):
return cmd[i + 1]
return None