mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-09-24 20:20:20 +01:00
111 lines
3.4 KiB
Python
111 lines
3.4 KiB
Python
"""A recording that never happened must say why.
|
|
|
|
Issue #1276: the debug bundle's recording/ directory was empty, and the only trace was
|
|
three "FFmpeg already stopped" debug lines - one per bypass, each logged 20-56s after
|
|
the recorder was started, meaning ffmpeg had exited almost immediately every time. It ran
|
|
with `-loglevel 0` and no stderr capture, so nothing anywhere recorded the reason. The
|
|
screen recording is the single most useful artifact for diagnosing a bypass failure.
|
|
"""
|
|
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
import shelfmark.bypass.internal_bypasser as ib
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clean_display():
|
|
before = dict(ib.DISPLAY)
|
|
ib.DISPLAY["ffmpeg"] = None
|
|
ib.DISPLAY["ffmpeg_output"] = None
|
|
ib.DISPLAY["ffmpeg_error_log"] = None
|
|
yield
|
|
ib.DISPLAY.update(before)
|
|
|
|
|
|
class _Proc:
|
|
def __init__(self, returncode):
|
|
self.returncode = returncode
|
|
|
|
def poll(self):
|
|
return self.returncode
|
|
|
|
|
|
def test_ffmpeg_errors_are_captured_to_a_file_beside_the_recording(monkeypatch, tmp_path):
|
|
monkeypatch.setattr(ib, "RECORDING_DIR", tmp_path)
|
|
captured: dict[str, object] = {}
|
|
|
|
def fake_popen(cmd, **kwargs):
|
|
captured["cmd"] = cmd
|
|
captured["stderr"] = kwargs.get("stderr")
|
|
return _Proc(None)
|
|
|
|
monkeypatch.setattr(ib.subprocess, "Popen", fake_popen)
|
|
|
|
ib._start_ffmpeg_recording(display=":99")
|
|
|
|
cmd = captured["cmd"]
|
|
# Errors must not be thrown away any more.
|
|
assert "-loglevel" in cmd
|
|
assert cmd[cmd.index("-loglevel") + 1] == "error"
|
|
# stderr goes to a real file, not a pipe nothing would drain.
|
|
assert captured["stderr"] is not None
|
|
assert captured["stderr"] is not subprocess.PIPE
|
|
|
|
error_log = ib.DISPLAY["ffmpeg_error_log"]
|
|
assert error_log is not None
|
|
assert error_log.parent == tmp_path
|
|
# It sits beside the mp4, so it travels in the debug bundle.
|
|
assert error_log.name.startswith("screen_recording_")
|
|
|
|
|
|
def test_an_early_exit_is_reported_with_ffmpegs_own_reason(monkeypatch, tmp_path, caplog):
|
|
reason = "[x11grab @ 0x1] Cannot open display :99, error 1."
|
|
error_log = tmp_path / "screen_recording_x.ffmpeg.log"
|
|
error_log.write_text(reason, encoding="utf-8")
|
|
|
|
ib.DISPLAY["ffmpeg"] = _Proc(1)
|
|
ib.DISPLAY["ffmpeg_output"] = tmp_path / "screen_recording_x.mp4"
|
|
ib.DISPLAY["ffmpeg_error_log"] = error_log
|
|
|
|
messages: list[str] = []
|
|
|
|
class _Capture:
|
|
def emit(self, record):
|
|
messages.append(record.getMessage())
|
|
|
|
import logging
|
|
|
|
handler = logging.Handler()
|
|
handler.emit = _Capture().emit # type: ignore[method-assign]
|
|
ib.logger.addHandler(handler)
|
|
previous = ib.logger.level
|
|
ib.logger.setLevel(logging.DEBUG)
|
|
ib.logger._cache.clear()
|
|
try:
|
|
ib._stop_ffmpeg_recording()
|
|
finally:
|
|
ib.logger.removeHandler(handler)
|
|
ib.logger.setLevel(previous)
|
|
|
|
line = next((m for m in messages if "exited early" in m), None)
|
|
assert line is not None, messages
|
|
assert "code 1" in line
|
|
assert "Cannot open display" in line
|
|
assert ib.DISPLAY["ffmpeg"] is None
|
|
|
|
|
|
def test_summary_is_explicit_when_ffmpeg_logged_nothing(tmp_path):
|
|
empty = tmp_path / "screen_recording_y.ffmpeg.log"
|
|
empty.write_text("", encoding="utf-8")
|
|
ib.DISPLAY["ffmpeg_error_log"] = empty
|
|
|
|
assert "logged nothing" in ib._ffmpeg_error_summary()
|
|
|
|
|
|
def test_summary_survives_a_missing_log():
|
|
ib.DISPLAY["ffmpeg_error_log"] = None
|
|
|
|
assert "No FFmpeg error log" in ib._ffmpeg_error_summary()
|