Four pieces of test debt, root-caused and hardened:
1. exports.py test coverage (was: zero dedicated tests): new
tests/test_exports_api.py (26 tests) covering /export, /export/record,
/export/history, /export/reveal — happy paths, traversal/containment
guards (incl. symlink escape), destination validation, error mapping,
and the mp4 watermark-overlay branch with its plain-copy fallback.
Two real bugs found and fixed in the router:
- _safe_destination checked isabs() on realpath()'s output, which is
always absolute — dead check; a relative destination silently exported
to a cwd-dependent location instead of the documented 400.
- _safe_source let "." / ".." through the basename guard (caught only
later by realpath containment as a confusing 404); now 400 up front.
2. CI-Linux fp16 default-dtype leak (test_prefers_vocals_over_mix,
test_final_dub_track_and_seg_wav_are_watermarked): not reproducible on
macOS — instrumenting torch.set_default_dtype across both tests records
zero non-fp32 sets locally. Both tests now carry an opt-in
torch_dtype_isolation fixture (save/restore, so the leak can never
spread), and the conftest guard is demoted to pure insurance. A cheap
permanent recorder wraps torch.set_default_dtype /
set_default_tensor_type once torch appears and captures the setter's
stack only on a non-fp32 set; both fixtures print that stack when they
fire, so the next CI occurrence names the exact culprit call chain.
3. Test-order pollution (both reported combos): root cause was
collection-time sys.modules stubbing in backend/tests — seven modules
installed bare ModuleType stubs for core.config (and test_capture_ws.py
for services.model_manager/asr_backend/ffmpeg_utils, now all lazily
imported by the router anyway). pytest imports test modules during
collection, so the stubs leaked process-wide before any test ran:
- combo (a): monkeypatch.setattr("core.config.OUTPUTS_DIR", ...) in
test_longform_e2e died with AttributeError (core never gets a .config
attribute when the import is satisfied straight from sys.modules).
- combo (b): test_router_smoke's `from main import app` died with
ImportError: cannot import name 'find_ffmpeg' (unknown location).
Fix at source: new backend/tests/conftest.py sets a hermetic
OMNIVOICE_DATA_DIR (mirroring tests/conftest.py, #878) and the real
core.config is imported everywhere — zero sys.modules surgery. New
backend/tests/test_no_module_stubs.py guards the whole class (verified
fail-before/pass-after against the old stub). Stale rationale comments
in pyproject.toml and ci.yml updated to match.
4. batched_tts.py TODO(#312): investigated, comment corrected only —
#312 is closed (the live routes are engine-aware); this module has zero
call sites and stays an unintegrated experiment. See PR notes.
Full tests/ suite: 2796 passed. backend/tests standalone: 130 passed.
Both pollution combos re-run green in the reported orderings.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
42 lines
2.0 KiB
Python
42 lines
2.0 KiB
Python
"""Shared setup for backend/tests — import path + hermetic data dir.
|
|
|
|
Historically every module in this directory stubbed
|
|
``sys.modules["core.config"]`` with a bare 3-4 attribute ``ModuleType``
|
|
pointing at its own ``mkdtemp``. That stub leaked **process-wide at
|
|
collection time**: pytest imports test modules while collecting, so in any
|
|
mixed invocation (``pytest tests/... backend/tests/...``) every *later* lazy
|
|
import of ``core.config`` resolved the stub instead of the real module —
|
|
``tests/test_router_smoke.py``'s ``from main import app`` died with
|
|
ImportError (missing config attrs), and
|
|
``monkeypatch.setattr("core.config.X", ...)`` died with AttributeError
|
|
(``core`` never gets a ``config`` attribute when the name is satisfied
|
|
straight from ``sys.modules``). That was the root cause of the
|
|
order-pollution combos around test_longform_e2e (8 AttributeErrors) and
|
|
test_router_smoke (24 fixture ImportErrors).
|
|
|
|
The real ``core.config`` derives every path from ``OMNIVOICE_DATA_DIR`` at
|
|
import time, so pointing that env var at a throwaway dir *before* any test
|
|
module imports it gives the same hermeticity (issue #878: never touch the
|
|
developer's real app state) with zero ``sys.modules`` surgery. This mirrors
|
|
``tests/conftest.py``; in a mixed run whichever conftest loads first wins
|
|
(``setdefault`` semantics) and both point at a throwaway tmpdir.
|
|
|
|
Do NOT reintroduce module-level ``sys.modules`` stubs in this directory —
|
|
import the real module and rely on this conftest instead.
|
|
"""
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
|
|
# Backend runs with `--app-dir backend`, so tests must do the same.
|
|
_BACKEND = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
if _BACKEND not in sys.path:
|
|
sys.path.insert(0, _BACKEND)
|
|
|
|
if not os.environ.get("OMNIVOICE_DATA_DIR"):
|
|
os.environ["OMNIVOICE_DATA_DIR"] = tempfile.mkdtemp(prefix="omnivoice-test-data-")
|
|
if not os.environ.get("OMNIVOICE_ENV_FILE"):
|
|
os.environ["OMNIVOICE_ENV_FILE"] = os.path.join(
|
|
os.environ["OMNIVOICE_DATA_DIR"], "user-env"
|
|
)
|