import os import shutil import subprocess import pytest # Let widget-level tests run headless; harmless for the non-GUI tests. os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") @pytest.fixture(autouse=True) def _isolate_eventlog(tmp_path, monkeypatch): """Keep the control-provenance log out of the real ~/.cache during tests (Player/MPRIS methods log unconditionally).""" from lintunes import eventlog monkeypatch.setattr(eventlog, "_log_path", lambda: tmp_path / "control-events.log") @pytest.fixture(scope="session") def qapp(): """A Qt application so QObject/QTimer and real widgets work in tests. Prefers a GUI QApplication (offscreen) so widget tests are possible; QApplication is a QCoreApplication, so existing non-GUI tests are unaffected. Falls back to QCoreApplication if no GUI platform is available. """ from PyQt6.QtCore import QCoreApplication inst = QCoreApplication.instance() if inst is not None: return inst try: from PyQt6.QtWidgets import QApplication return QApplication([]) except Exception: return QCoreApplication([]) def _generate_audio(path, codec_args): """Generate a 1-second sine-wave audio file with ffmpeg.""" if shutil.which("ffmpeg") is None: pytest.skip("ffmpeg not available to generate audio fixtures") result = subprocess.run( ["ffmpeg", "-y", "-f", "lavfi", "-i", "sine=frequency=440:duration=1", *codec_args, str(path)], capture_output=True, ) if result.returncode != 0: pytest.skip(f"ffmpeg could not generate {path.suffix} fixture") return path @pytest.fixture def mp3_file(tmp_path): return _generate_audio(tmp_path / "test.mp3", ["-codec:a", "libmp3lame", "-b:a", "64k"]) @pytest.fixture def m4a_file(tmp_path): return _generate_audio(tmp_path / "test.m4a", ["-codec:a", "aac", "-b:a", "64k"]) @pytest.fixture def flac_file(tmp_path): return _generate_audio(tmp_path / "test.flac", ["-codec:a", "flac"]) @pytest.fixture def jpeg_bytes(tmp_path): """A tiny valid JPEG for artwork tests.""" if shutil.which("ffmpeg") is None: pytest.skip("ffmpeg not available to generate image fixture") path = tmp_path / "art.jpg" result = subprocess.run( ["ffmpeg", "-y", "-f", "lavfi", "-i", "color=red:size=8x8", "-frames:v", "1", str(path)], capture_output=True, ) if result.returncode != 0: pytest.skip("ffmpeg could not generate jpeg fixture") return path.read_bytes() # ---- real-Syncthing tests (Cassette) ---- # Two throwaway Syncthing instances on loopback (scripts/cassette_pair.py). # Slow-ish (a handshake takes Syncthing some seconds), so opt-in: # python3 -m pytest -m syncthing --syncthing def pytest_addoption(parser): parser.addoption("--syncthing", action="store_true", help="run tests that start two real Syncthing instances") def pytest_configure(config): config.addinivalue_line( "markers", "syncthing: starts two real Syncthing instances " "(opt in with --syncthing)") def pytest_collection_modifyitems(config, items): if config.getoption("--syncthing"): return skip = pytest.mark.skip(reason="needs --syncthing") for item in items: if "syncthing" in item.keywords: item.add_marker(skip) @pytest.fixture def syncthing_pair(tmp_path): """[alice, bob, carol] Instances from scripts/cassette_pair.py, stopped after.""" if shutil.which("syncthing") is None: pytest.skip("syncthing isn't installed") import importlib.util from pathlib import Path spec = importlib.util.spec_from_file_location( "cassette_pair", Path(__file__).resolve().parent.parent / "scripts" / "cassette_pair.py") pair = importlib.util.module_from_spec(spec) import sys sys.modules[spec.name] = pair # dataclasses look their module up there spec.loader.exec_module(pair) out = tmp_path / "pair" instances = pair.start(out, names=("alice", "bob", "carol")) try: yield instances finally: pair.stop(out)