Some checks failed
Contributor Attribution Check / check-attribution (pull_request) Failing after 40s
Docker Build and Publish / build-and-push (pull_request) Has been skipped
Supply Chain Audit / Scan PR for supply chain risks (pull_request) Successful in 30s
Tests / e2e (pull_request) Successful in 2m0s
Tests / test (pull_request) Failing after 36m24s
106 lines
3.2 KiB
Python
106 lines
3.2 KiB
Python
"""Tests for shared audio analysis engine.
|
|
|
|
Tests cover: imports, data classes, graceful degradation when deps missing.
|
|
Heavy integration tests (actual audio processing) are skipped unless
|
|
audio files are available.
|
|
"""
|
|
|
|
import pytest
|
|
import sys
|
|
import os
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
|
|
|
from tools.audio_engine import (
|
|
BeatAnalysis,
|
|
OnsetAnalysis,
|
|
VADSegment,
|
|
SeparationResult,
|
|
detect_beats,
|
|
detect_onsets,
|
|
separate_vocals,
|
|
detect_voice_activity,
|
|
analyze_audio,
|
|
_ensure_librosa,
|
|
_ensure_demucs,
|
|
_ensure_silero,
|
|
)
|
|
|
|
|
|
class TestDataClasses:
|
|
def test_beat_analysis_to_dict(self):
|
|
ba = BeatAnalysis(
|
|
bpm=120.0,
|
|
beat_times=[0.0, 0.5, 1.0],
|
|
beat_frames=[0, 100, 200],
|
|
tempo_confidence=0.8,
|
|
duration=3.0,
|
|
sample_rate=22050,
|
|
)
|
|
d = ba.to_dict()
|
|
assert d["bpm"] == 120.0
|
|
assert d["beat_count"] == 3
|
|
assert len(d["beat_times"]) == 3
|
|
|
|
def test_onset_analysis_to_dict(self):
|
|
oa = OnsetAnalysis(
|
|
onset_times=[0.1, 0.5],
|
|
onset_frames=[10, 50],
|
|
onset_count=2,
|
|
avg_onset_interval=0.4,
|
|
)
|
|
d = oa.to_dict()
|
|
assert d["onset_count"] == 2
|
|
assert d["avg_onset_interval"] == 0.4
|
|
|
|
def test_vad_segment_to_dict(self):
|
|
seg = VADSegment(start=1.0, end=2.5, is_speech=True)
|
|
d = seg.to_dict()
|
|
assert d["start"] == 1.0
|
|
assert d["end"] == 2.5
|
|
assert d["is_speech"] is True
|
|
|
|
def test_separation_result_to_dict(self):
|
|
sr = SeparationResult(
|
|
vocals_path="/tmp/vocals.wav",
|
|
instrumental_path="/tmp/inst.wav",
|
|
duration=120.0,
|
|
)
|
|
d = sr.to_dict()
|
|
assert d["vocals_path"] == "/tmp/vocals.wav"
|
|
assert d["duration"] == 120.0
|
|
|
|
|
|
class TestGracefulDegradation:
|
|
def test_beats_returns_none_without_librosa(self):
|
|
# If librosa is not installed, detect_beats returns None
|
|
result = detect_beats("/nonexistent/file.wav")
|
|
# Either None (no librosa) or None (file not found) — both acceptable
|
|
assert result is None or isinstance(result, BeatAnalysis)
|
|
|
|
def test_onsets_returns_none_without_librosa(self):
|
|
result = detect_onsets("/nonexistent/file.wav")
|
|
assert result is None or isinstance(result, OnsetAnalysis)
|
|
|
|
def test_separation_returns_none_without_demucs(self):
|
|
result = separate_vocals("/nonexistent/file.wav")
|
|
assert result is None or isinstance(result, SeparationResult)
|
|
|
|
def test_vad_returns_none_without_silero(self):
|
|
result = detect_voice_activity("/nonexistent/file.wav")
|
|
assert result is None or isinstance(result, list)
|
|
|
|
|
|
class TestDependencyChecks:
|
|
def test_ensure_librosa_returns_none_or_module(self):
|
|
result = _ensure_librosa()
|
|
assert result is None or result is not None # Either is fine
|
|
|
|
def test_ensure_demucs_is_bool(self):
|
|
result = _ensure_demucs()
|
|
assert isinstance(result, bool)
|
|
|
|
def test_ensure_silero_is_bool(self):
|
|
result = _ensure_silero()
|
|
assert isinstance(result, bool)
|