235 lines
8.7 KiB
Python
235 lines
8.7 KiB
Python
"""
|
|
Smoke-test coverage for ``_av1an_vsscript_smoke_test``.
|
|
|
|
QA finding: OTC-001 (critical).
|
|
|
|
The v1 implementation of the av1an VSScript smoke test returned ``True`` for
|
|
any non-VSScript failure, masking real bugs (missing encoder binary, concat-
|
|
method mismatch, av1an panic, etc.). The pre-flight check therefore reported
|
|
"OK" and the per-file loop then failed for every file — the
|
|
"chunks but never saves a file" symptom.
|
|
|
|
The v3 implementation (this is what we're testing) classifies failure modes
|
|
and returns ``False`` for unknown failures. The critical regression test is
|
|
``test_smoke_returns_false_on_unknown_failure``: it feeds the function a
|
|
generic rc=1 + non-VSScript stderr and verifies the function now returns
|
|
``False`` (the v1 bug was returning ``True`` here).
|
|
|
|
All 5 cases run without a real av1an / ffmpeg install: ``subprocess.run`` is
|
|
mocked via ``monkeypatch.setattr("subprocess.run", ...)`` and the smoke-test
|
|
function's ``test_in.exists()`` / ``test_out.exists()`` checks are satisfied
|
|
by the mock side-effect creating the expected files at the in/out paths that
|
|
the function passes on the command line.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Helpers
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
def _make_smoke_side_effect(scenario: str):
|
|
"""Build a side_effect callable for ``subprocess.run``.
|
|
|
|
The smoke-test function calls ``subprocess.run`` exactly twice:
|
|
1. ffmpeg gen-cmd — last argument is the output path (``test_in``).
|
|
2. av1an cmd — output path follows ``-o``.
|
|
|
|
For "happy" the side_effect creates both files so the function's
|
|
``Path.exists()`` checks pass. For failure scenarios it creates only
|
|
``test_in`` (so the function proceeds past the gen step) and returns
|
|
the appropriate ``CompletedProcess`` for the av1an call.
|
|
"""
|
|
call_count = [0]
|
|
|
|
def side_effect(cmd, *args, **kwargs):
|
|
i = call_count[0]
|
|
call_count[0] += 1
|
|
|
|
if i == 0:
|
|
# ffmpeg gen-cmd — always rc=0; create the test_in file so
|
|
# `test_in.exists()` returns True inside the smoke-test.
|
|
Path(cmd[-1]).write_bytes(b"\x00fake-video\x00")
|
|
return subprocess.CompletedProcess(
|
|
args=cmd, returncode=0, stdout="", stderr="",
|
|
)
|
|
|
|
# i == 1 — av1an cmd
|
|
if scenario == "happy":
|
|
# Create test_out so `test_out.exists()` returns True.
|
|
out_idx = cmd.index("-o") + 1
|
|
Path(cmd[out_idx]).write_bytes(b"\x00fake-encode\x00")
|
|
return subprocess.CompletedProcess(
|
|
args=cmd, returncode=0, stdout="", stderr="",
|
|
)
|
|
if scenario == "vsscript_incompat":
|
|
return subprocess.CompletedProcess(
|
|
args=cmd, returncode=1, stdout="",
|
|
stderr="Error: Failed to get VSScript API. ABI mismatch.",
|
|
)
|
|
if scenario == "invalid_encoder":
|
|
return subprocess.CompletedProcess(
|
|
args=cmd, returncode=1, stdout="",
|
|
stderr="error: invalid value 'foo' for '--encoder <ENCODER>'",
|
|
)
|
|
if scenario == "unknown_failure":
|
|
return subprocess.CompletedProcess(
|
|
args=cmd, returncode=1, stdout="some av1an stdout",
|
|
stderr="panic at src/encode.rs:42\nunknown failure mode",
|
|
)
|
|
if scenario == "timeout":
|
|
raise subprocess.TimeoutExpired(cmd=cmd, timeout=30)
|
|
raise ValueError(f"unknown scenario: {scenario!r}")
|
|
|
|
return side_effect
|
|
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Test cases
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
AV1AN_FLAGS = {
|
|
"worker": "--workers",
|
|
"video_params": "--video-params",
|
|
"audio_params": "--audio-params",
|
|
"concat_method": "ffmpeg",
|
|
"has_chunk_method": True,
|
|
}
|
|
|
|
|
|
def test_smoke_returns_true_on_success(v3_module, monkeypatch):
|
|
"""Happy path: ffmpeg gen rc=0 + av1an rc=0 + output file exists
|
|
-> returns ``(True, "av1an VSScript init OK")``.
|
|
"""
|
|
monkeypatch.setattr(
|
|
"subprocess.run",
|
|
MagicMock(side_effect=_make_smoke_side_effect("happy")),
|
|
)
|
|
|
|
ok, detail = v3_module._av1an_vsscript_smoke_test(
|
|
av1an_bin="/fake/av1an",
|
|
ffmpeg_bin="/fake/ffmpeg",
|
|
av1an_flags=AV1AN_FLAGS,
|
|
svt_name="svt_av1",
|
|
timeout=5,
|
|
)
|
|
|
|
assert ok is True
|
|
assert detail == "av1an VSScript init OK"
|
|
|
|
|
|
def test_smoke_returns_false_on_vsscript_incompat(v3_module, monkeypatch):
|
|
"""stderr contains "Failed to get VSScript API"
|
|
-> returns ``(False, "VSScript_API_INCOMPAT")``.
|
|
"""
|
|
monkeypatch.setattr(
|
|
"subprocess.run",
|
|
MagicMock(side_effect=_make_smoke_side_effect("vsscript_incompat")),
|
|
)
|
|
|
|
ok, detail = v3_module._av1an_vsscript_smoke_test(
|
|
av1an_bin="/fake/av1an",
|
|
ffmpeg_bin="/fake/ffmpeg",
|
|
av1an_flags=AV1AN_FLAGS,
|
|
svt_name="svt_av1",
|
|
timeout=5,
|
|
)
|
|
|
|
assert ok is False
|
|
assert detail == "VSScript_API_INCOMPAT"
|
|
|
|
|
|
def test_smoke_returns_false_on_invalid_encoder(v3_module, monkeypatch):
|
|
"""stderr contains both "invalid value" and "--encoder"
|
|
-> returns ``(False, "INVALID_ENCODER: ...")``.
|
|
"""
|
|
monkeypatch.setattr(
|
|
"subprocess.run",
|
|
MagicMock(side_effect=_make_smoke_side_effect("invalid_encoder")),
|
|
)
|
|
|
|
ok, detail = v3_module._av1an_vsscript_smoke_test(
|
|
av1an_bin="/fake/av1an",
|
|
ffmpeg_bin="/fake/ffmpeg",
|
|
av1an_flags=AV1AN_FLAGS,
|
|
svt_name="svt_av1",
|
|
timeout=5,
|
|
)
|
|
|
|
assert ok is False
|
|
assert detail.startswith("INVALID_ENCODER:")
|
|
assert "invalid value" in detail
|
|
assert "--encoder" in detail
|
|
|
|
|
|
def test_smoke_returns_false_on_unknown_failure(v3_module, monkeypatch):
|
|
"""**CRITICAL OTC-001 REGRESSION TEST**.
|
|
|
|
A generic rc=1 with non-VSScript stderr MUST return ``False``. The v1
|
|
implementation returned ``True`` here, masking real bugs (missing encoder
|
|
binary, concat-method mismatch, av1an panic, etc.) and causing the
|
|
"chunks but never saves a file" symptom in production.
|
|
|
|
v3 must classify this as ``SMOKE_FAIL`` so the caller can offer ffmpeg
|
|
fallback or abort with an actionable message (SEI CERT ERR01-C: never
|
|
mask a failure as success).
|
|
"""
|
|
monkeypatch.setattr(
|
|
"subprocess.run",
|
|
MagicMock(side_effect=_make_smoke_side_effect("unknown_failure")),
|
|
)
|
|
|
|
ok, detail = v3_module._av1an_vsscript_smoke_test(
|
|
av1an_bin="/fake/av1an",
|
|
ffmpeg_bin="/fake/ffmpeg",
|
|
av1an_flags=AV1AN_FLAGS,
|
|
svt_name="svt_av1",
|
|
timeout=5,
|
|
)
|
|
|
|
# The whole point of OTC-001: this MUST be False, never True.
|
|
assert ok is False, (
|
|
"OTC-001 REGRESSION: smoke test returned True for an unknown "
|
|
"failure. v1 had this bug and it caused 'chunks but never saves a "
|
|
"file' in production. v3 must return False here."
|
|
)
|
|
assert detail.startswith("SMOKE_FAIL"), (
|
|
f"Expected SMOKE_FAIL detail prefix, got: {detail!r}"
|
|
)
|
|
assert "rc=1" in detail
|
|
# v3 includes the FULL stderr (not just the tail) so the user can see
|
|
# the actual error and the diagnostic patterns can match on it.
|
|
assert "unknown failure mode" in detail
|
|
|
|
|
|
def test_smoke_returns_false_on_timeout(v3_module, monkeypatch):
|
|
"""``subprocess.TimeoutExpired`` raised
|
|
-> returns ``(False, "SMOKE_TIMEOUT: ...")``.
|
|
|
|
v3 does NOT mask a timeout as success — a hanging av1an is a real
|
|
failure that the user must be told about (SEI CERT ERR01-C).
|
|
"""
|
|
monkeypatch.setattr(
|
|
"subprocess.run",
|
|
MagicMock(side_effect=_make_smoke_side_effect("timeout")),
|
|
)
|
|
|
|
ok, detail = v3_module._av1an_vsscript_smoke_test(
|
|
av1an_bin="/fake/av1an",
|
|
ffmpeg_bin="/fake/ffmpeg",
|
|
av1an_flags=AV1AN_FLAGS,
|
|
svt_name="svt_av1",
|
|
timeout=5,
|
|
)
|
|
|
|
assert ok is False
|
|
assert detail.startswith("SMOKE_TIMEOUT:")
|
|
assert "5s" in detail or "5" in detail
|