LinTunes has spontaneously resumed playback for ~4s (then paused) four times while trav was away — suspected phantom media-key events from the Audioengine USB DAC's HID 'keyboard', but the pathway (MPRIS vs the focused-window key filter) is unproven. Every control path now drops a timestamped line in ~/.cache/lintunes/control-events.log: MPRIS Player methods, the media-key eventFilter (with source input device where the compositor exposes it), and Player.toggle_play/pause with position. New lintunes/eventlog.py, 1MB rotation, never raises; conftest autouse fixture keeps tests off the real log file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
82 lines
2.5 KiB
Python
82 lines
2.5 KiB
Python
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()
|