fix: make container media diagnostics reliable
This commit is contained in:
+4
-1
@@ -1,7 +1,10 @@
|
||||
ARG M4B_TOOL_IMAGE=sandreas/m4b-tool:0.5.2
|
||||
FROM ${M4B_TOOL_IMAGE}
|
||||
|
||||
RUN apk add --no-cache python3 py3-pip
|
||||
# Alpine's ffmpeg package supplies ffprobe. /usr/local/bin/ffmpeg from the
|
||||
# upstream image remains first on PATH for its specialized codec build.
|
||||
RUN apk add --no-cache ffmpeg python3 py3-pip \
|
||||
&& ffprobe -version
|
||||
|
||||
WORKDIR /app
|
||||
COPY pyproject.toml /app/
|
||||
|
||||
+10
-1
@@ -18,6 +18,11 @@ from .tui import AudiobookApp, TerminalUI
|
||||
|
||||
_DEFAULT_CONFIG = Path("config.toml")
|
||||
_EXECUTABLES = ("m4b-tool", "ffmpeg", "ffprobe")
|
||||
_VERSION_ARGUMENTS = {
|
||||
"m4b-tool": ("--version",),
|
||||
"ffmpeg": ("-version",),
|
||||
"ffprobe": ("-version",),
|
||||
}
|
||||
|
||||
|
||||
def _parser() -> argparse.ArgumentParser:
|
||||
@@ -43,7 +48,11 @@ def _version(executable: str) -> str | None:
|
||||
return None
|
||||
try:
|
||||
result = subprocess.run(
|
||||
(executable, "--version"), text=True, capture_output=True, check=False, timeout=5
|
||||
(executable, *_VERSION_ARGUMENTS[executable]),
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
timeout=5,
|
||||
)
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
return None
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
set -eu
|
||||
|
||||
m4b-tool --version
|
||||
ffmpeg -version
|
||||
ffprobe -version
|
||||
|
||||
merge_help=$(m4b-tool merge --help 2>&1)
|
||||
for required_option in \
|
||||
--jobs \
|
||||
@@ -26,8 +29,6 @@ done
|
||||
if [ "${AUDIOBOOK_SMOKE_OPTION_GUARD_ONLY:-0}" = 1 ]; then
|
||||
exit 0
|
||||
fi
|
||||
ffmpeg -version
|
||||
ffprobe -version
|
||||
|
||||
smoke_root=$(mktemp -d "${TMPDIR:-/tmp}/audiobook-tui-smoke.XXXXXX")
|
||||
trap 'rm -rf "$smoke_root"' EXIT HUP INT TERM
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import app.main as main_module
|
||||
from app.models import Roots, Settings
|
||||
|
||||
@@ -20,6 +22,33 @@ def test_doctor_is_read_only_and_reports_missing_config(tmp_path: Path, capsys)
|
||||
assert "configuration" in capsys.readouterr().out.lower()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("executable", "expected_argument"),
|
||||
(("m4b-tool", "--version"), ("ffmpeg", "-version"), ("ffprobe", "-version")),
|
||||
)
|
||||
def test_version_uses_each_executables_supported_version_argument(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
executable: str,
|
||||
expected_argument: str,
|
||||
) -> None:
|
||||
binary_directory = tmp_path / "bin"
|
||||
binary_directory.mkdir()
|
||||
fake = binary_directory / executable
|
||||
fake.write_text(
|
||||
"#!/bin/sh\n"
|
||||
f'if [ "$#" -ne 1 ] || [ "$1" != "{expected_argument}" ]; then\n'
|
||||
" exit 64\n"
|
||||
"fi\n"
|
||||
f"printf '%s\\n' '{executable} expected version'\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
fake.chmod(0o755)
|
||||
monkeypatch.setenv("PATH", str(binary_directory))
|
||||
|
||||
assert main_module._version(executable) == f"{executable} expected version"
|
||||
|
||||
|
||||
def test_doctor_checks_an_existing_destination_root_not_just_its_parent(tmp_path: Path) -> None:
|
||||
pending = tmp_path / "pending"
|
||||
pending.mkdir()
|
||||
|
||||
@@ -27,6 +27,27 @@ def _service() -> dict[str, object]:
|
||||
return compose["services"]["audiobook-tui"]
|
||||
|
||||
|
||||
def _write_fake_media_versions(
|
||||
binary_directory: Path, *, ffprobe_available: bool = True
|
||||
) -> None:
|
||||
for executable in ("ffmpeg", "ffprobe"):
|
||||
fake = binary_directory / executable
|
||||
if executable == "ffprobe" and not ffprobe_available:
|
||||
body = (
|
||||
"#!/bin/sh\n"
|
||||
"printf '%s\\n' 'ffprobe unavailable' >&2\n"
|
||||
"exit 127\n"
|
||||
)
|
||||
else:
|
||||
body = (
|
||||
"#!/bin/sh\n"
|
||||
"[ \"${1:-}\" = -version ] || exit 64\n"
|
||||
f"printf '%s\\n' '{executable} fake'\n"
|
||||
)
|
||||
fake.write_text(body, encoding="utf-8")
|
||||
fake.chmod(0o755)
|
||||
|
||||
|
||||
def test_compose_has_tty_stdin_and_four_mounts() -> None:
|
||||
service = _service()
|
||||
|
||||
@@ -130,6 +151,17 @@ def test_image_pin_and_application_entrypoint_are_explicit() -> None:
|
||||
assert 'ENTRYPOINT ["/usr/local/bin/audiobook-entrypoint"]' in dockerfile
|
||||
|
||||
|
||||
def test_image_installs_ffprobe_and_smoke_checks_its_supported_version_flag() -> None:
|
||||
dockerfile = Path("Dockerfile").read_text(encoding="utf-8").replace("\\\n", " ")
|
||||
smoke = Path("scripts/container-smoke.sh").read_text(encoding="utf-8")
|
||||
runtime_install = next(
|
||||
line for line in dockerfile.splitlines() if line.startswith("RUN apk add ")
|
||||
)
|
||||
|
||||
assert "ffmpeg" in runtime_install.split()
|
||||
assert "ffprobe -version" in smoke
|
||||
|
||||
|
||||
def test_container_smoke_script_is_copied_to_its_documented_path() -> None:
|
||||
dockerfile = Path("Dockerfile").read_text(encoding="utf-8")
|
||||
smoke = Path("scripts/container-smoke.sh").read_text(encoding="utf-8")
|
||||
@@ -162,6 +194,7 @@ def test_container_smoke_upgrade_guard_rejects_each_missing_planner_option(
|
||||
encoding="utf-8",
|
||||
)
|
||||
fake_m4b.chmod(0o755)
|
||||
_write_fake_media_versions(binary_directory)
|
||||
help_text = " ".join(option for option in PLANNER_OPTIONS if option != missing)
|
||||
|
||||
result = subprocess.run(
|
||||
@@ -197,6 +230,7 @@ def test_container_smoke_upgrade_guard_accepts_complete_planner_interface(
|
||||
encoding="utf-8",
|
||||
)
|
||||
fake_m4b.chmod(0o755)
|
||||
_write_fake_media_versions(binary_directory)
|
||||
|
||||
result = subprocess.run(
|
||||
("sh", "scripts/container-smoke.sh"),
|
||||
@@ -212,3 +246,36 @@ def test_container_smoke_upgrade_guard_accepts_complete_planner_interface(
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
|
||||
|
||||
def test_container_smoke_preflight_rejects_unavailable_ffprobe(tmp_path: Path) -> None:
|
||||
binary_directory = tmp_path / "bin"
|
||||
binary_directory.mkdir()
|
||||
fake_m4b = binary_directory / "m4b-tool"
|
||||
fake_m4b.write_text(
|
||||
"#!/bin/sh\n"
|
||||
"if [ \"${1:-}\" = merge ] && [ \"${2:-}\" = --help ]; then\n"
|
||||
" printf '%s\\n' \"$FAKE_M4B_HELP\"\n"
|
||||
"else\n"
|
||||
" printf '%s\\n' 'm4b-tool fake'\n"
|
||||
"fi\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
fake_m4b.chmod(0o755)
|
||||
_write_fake_media_versions(binary_directory, ffprobe_available=False)
|
||||
|
||||
result = subprocess.run(
|
||||
("sh", "scripts/container-smoke.sh"),
|
||||
env={
|
||||
**os.environ,
|
||||
"PATH": f"{binary_directory}:{os.environ['PATH']}",
|
||||
"FAKE_M4B_HELP": " ".join(PLANNER_OPTIONS),
|
||||
"AUDIOBOOK_SMOKE_OPTION_GUARD_ONLY": "1",
|
||||
},
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
assert result.returncode == 127
|
||||
assert "ffprobe unavailable" in result.stderr
|
||||
|
||||
Reference in New Issue
Block a user