Files
hermes-agent/tests/test_audio_engine.py
Alexander Whitestone dd0cf8abe9
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
test: add audio engine tests (#644)
2026-04-15 01:08:45 +00:00

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)