Files
VoiceStudio/tests/test_failure_classify.py
T
2339cc8e85 fix(model): actionable "reinstall transformers" hint on a corrupted-install model-load error (#676)
A model load failed with `[Errno 2] No such file or directory:
'…/site-packages/transformers/models/qwen3/modeling_qwen3.py'` — the user's
transformers install was incomplete (the file is missing while a correct 5.3.0
install has it; an interrupted `uv sync` / antivirus / partial update drops it).
The System Check showed the raw path + "Check logs and try restarting", which is
useless — restarting can't restore a missing file.

Two fixes:
1. core.failure.classify(): recognize this corrupted-install variant. It's a
   FileNotFoundError, not an ImportError, so the existing TRANSFORMERS_IMPORT
   match ("could not import module"/"AutoFeatureExtractor") missed it. Now also
   matches a "no such file"/"errno 2" + "transformers" + "site-packages" signal
   (substrings checked separately so it works on POSIX `/` and Windows `\`
   paths). An unrelated package's missing file is NOT mislabelled.
2. model_manager._load(): build the /model/status error via build_failure so it
   carries the classified hint AND strips the home dir, instead of storing the
   raw str(exc). The System Check now shows "Your transformers install is
   incomplete. Reinstall it (uv pip install --reinstall transformers) or switch
   ASR to faster-whisper" — the existing TRANSFORMERS_IMPORT hint.

Docs: troubleshooting §1a documents the error + the reinstall fix.

Test: test_failure_classify.py pins the POSIX + Windows path forms classify as
TRANSFORMERS_IMPORT with a "reinstall" hint, and that an unrelated package's
missing file does not.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 14:56:29 +05:30

103 lines
4.7 KiB
Python

"""ASR-robustness failure classification (#551 / #549).
The dub/transcribe "no segments" toast is only actionable if `classify()` names
the failure class so `build_failure()` can attach a hint. These assert the two
new taxonomy classes added for the ASR-robustness fix map to a non-empty hint.
"""
from core import failure
def test_classify_compute_type_unsupported():
# The exact CTranslate2 message on a GPU without efficient fp16 (#551).
reason = (
"Requested float16 compute type, but the target device or backend do "
"not support efficient float16 computation"
)
assert failure.classify(reason) == "COMPUTE_TYPE_UNSUPPORTED"
evt = failure.build_failure(reason, stage="transcribe", include_diagnostic=False)
assert evt["docs_topic"] == "COMPUTE_TYPE_UNSUPPORTED"
assert evt["hint"], "compute-type failure must carry an actionable hint"
def test_classify_transformers_import():
# The transformers ASR-pipeline import failure (#549).
assert failure.classify("Could not import module 'AutoFeatureExtractor'") == (
"TRANSFORMERS_IMPORT"
)
# Substring match on the bare class name too (case-insensitive).
assert failure.classify("AutoFeatureExtractor failed to load") == "TRANSFORMERS_IMPORT"
evt = failure.build_failure(
"Could not import module 'AutoFeatureExtractor'",
stage="transcribe",
include_diagnostic=False,
)
assert evt["hint"], "transformers-import failure must carry an actionable hint"
def test_classify_corrupted_transformers_file():
# A missing transformers module file (interrupted uv sync / AV / partial
# update) surfaces as FileNotFoundError, not ImportError — it must still
# classify as TRANSFORMERS_IMPORT so the user gets "reinstall", not "restart".
posix = (
"[Errno 2] No such file or directory: "
"'/Users/u/Library/Application Support/com.x/project/.venv/lib/python3.11/"
"site-packages/transformers/models/qwen3/modeling_qwen3.py'"
)
win = (
"[Errno 2] No such file or directory: "
r"'C:\Users\u\AppData\Local\com.x\project\.venv\Lib\site-packages\transformers"
r"\models\qwen3\modeling_qwen3.py'"
)
assert failure.classify(posix) == "TRANSFORMERS_IMPORT"
assert failure.classify(win) == "TRANSFORMERS_IMPORT"
f = failure.build_failure(FileNotFoundError(posix), stage="model-load", include_diagnostic=False)
assert "reinstall" in f["hint"].lower()
# A missing file from an UNRELATED package must NOT be mislabelled as transformers.
assert failure.classify("[Errno 2] No such file or directory: '/x/site-packages/numpy/core/foo.py'") == ""
def test_classify_video_download_classes():
# #554: a non-downloadable link shape → actionable "paste a direct video URL".
assert failure.classify("Unsupported URL: https://www.douyin.com/discover") == (
"UNSUPPORTED_VIDEO_URL"
)
# #536: a transient mid-download drop → "just retry".
assert failure.classify("Unable to download video: [Errno 32] Broken pipe") == (
"VIDEO_DOWNLOAD_NETWORK"
)
assert failure.classify("Connection reset by peer") == "VIDEO_DOWNLOAD_NETWORK"
for cls, reason in (
("UNSUPPORTED_VIDEO_URL", "Unsupported URL: x"),
("VIDEO_DOWNLOAD_NETWORK", "Unable to download video: Broken pipe"),
):
evt = failure.build_failure(reason, stage="download", include_diagnostic=False)
assert evt["docs_topic"] == cls
assert evt["hint"], f"{cls} must carry an actionable hint"
def test_classify_broken_venv_encodings():
# The relocated/corrupted-venv stdlib-bootstrap failure → BROKEN_VENV (the
# Rust self-heal rebuilds it; this names the class for the toast).
assert failure.classify("ModuleNotFoundError: No module named 'encodings'") == (
"BROKEN_VENV"
)
# ...but an app-level import of an 'encodings'-prefixed package must NOT.
assert failure.classify("No module named 'encodings_helper'") == ""
def test_classify_broken_venv_missing_own_package():
# #564: the interpreter starts but the backend can't import its own
# 'omnivoice' package (editable install missing) → BROKEN_VENV so the toast
# points at the self-heal / Clean & Retry instead of a bare import error.
assert failure.classify("ModuleNotFoundError: No module named 'omnivoice'") == (
"BROKEN_VENV"
)
# ...but a legitimately-named 'omnivoice_*' helper package must NOT match
# (the trailing quote in the matcher is the guard).
assert failure.classify("No module named 'omnivoice_helper'") == ""
def test_classify_generic_still_empty():
# A genuinely unknown reason must still classify to "" (no false hint).
assert failure.classify("some totally unrelated failure") == ""