Files
voculinux/tests/test_audio_feedback.py
T
xavierk 80a3792576
CI / security (pull_request) Failing after 1m25s
CI / lint (pull_request) Failing after 34s
CI / ai-review (pull_request) Successful in 25s
chore: import vocalinux project
Import the vocalinux source tree (tray app, Next.js site in web/,
packaging, installers, docs and tests) on top of the Gitea template
commit. The project README replaces the template README, and the
template CI files (.gitea/workflows/ci.yml, .jscpd.json, CI.md) are kept.
2026-11-06 11:51:44 +05:30

719 lines
29 KiB
Python

"""
Tests for the audio feedback functionality.
"""
import os
import sys
import tempfile
import unittest
import wave
from typing import Any
from unittest.mock import patch
import pytest
# We need to use absolute paths for patching in module scope
AUDIO_FEEDBACK_MODULE = "vocalinux.ui.audio_feedback"
# Pytest collects files in command-line order, so this module may import after
# tests that replace tempfile/wave in sys.modules with MagicMock. Rebind to
# the real stdlib modules for WAV fixtures. Leave real tempfile in sys.modules
# so a later `from tempfile import TemporaryDirectory` is not a MagicMock.
# Do not pop tempfile in the per-test fixture below.
if getattr(tempfile, "__name__", None) != "tempfile":
sys.modules.pop("tempfile", None)
import tempfile
if getattr(wave, "__name__", None) != "wave":
sys.modules.pop("wave", None)
import wave
@pytest.fixture(autouse=True)
def reset_audio_module():
"""Reset the audio_feedback module before each test to allow proper testing."""
# Sibling tests replace sys.modules["wave"] with a MagicMock. Pin the real
# module this file imported so a reimported audio_feedback gets wave.open.
previous_wave = sys.modules.get("wave")
sys.modules["wave"] = wave
# Remove the mock that conftest installs
if AUDIO_FEEDBACK_MODULE in sys.modules:
del sys.modules[AUDIO_FEEDBACK_MODULE]
try:
yield
finally:
if previous_wave is None:
sys.modules.pop("wave", None)
else:
sys.modules["wave"] = previous_wave
# Restore the mock after test for other tests that need it
from conftest import mock_audio_feedback
sys.modules[AUDIO_FEEDBACK_MODULE] = mock_audio_feedback
def _write_test_wav(
path: str,
*,
nframes: int = 80,
framerate: int = 8000,
nchannels: int = 1,
sampwidth: int = 2,
payload: bytes | None = None,
) -> str:
"""Write a tiny PCM WAV fixture and return ``path``."""
if payload is None:
payload = b"\x01\x00" * (nframes * nchannels)
with wave.open(path, "wb") as wav_file:
wav_file.setnchannels(nchannels)
wav_file.setsampwidth(sampwidth)
wav_file.setframerate(framerate)
wav_file.writeframes(payload)
return path
def _popen_argv(mock_popen: Any) -> list[str]:
args, _kwargs = mock_popen.call_args
return list(args[0])
def _assert_played_wav(argv: list[str], player: str, extra_flags: tuple[str, ...] = ()) -> str:
"""Assert player argv shape and return the wav path that was played."""
assert argv[0] == player
assert argv[1 : 1 + len(extra_flags)] == list(extra_flags)
played = argv[-1]
assert str(played).endswith(".wav")
return played
class TestAudioFeedback(unittest.TestCase):
"""Test cases for audio feedback functionality."""
def test_resource_paths(self):
"""Test that resource paths are correctly set up."""
# Import fresh module
import vocalinux.ui.audio_feedback as audio_feedback
# Import the resource manager to test paths
from vocalinux.utils.resource_manager import ResourceManager
resource_manager = ResourceManager()
# Verify that resource paths are correctly set and accessible
self.assertTrue(
resource_manager.resources_dir.endswith("resources"),
f"Resources directory is not valid: {resource_manager.resources_dir}",
)
self.assertTrue(
resource_manager.sounds_dir.endswith("sounds"),
f"Sounds directory path is not valid: {resource_manager.sounds_dir}",
)
self.assertEqual(os.path.basename(audio_feedback.START_SOUND), "start_recording.wav")
self.assertEqual(os.path.basename(audio_feedback.STOP_SOUND), "stop_recording.wav")
self.assertEqual(os.path.basename(audio_feedback.ERROR_SOUND), "error.wav")
def test_get_audio_player_pulseaudio(self):
"""Test detecting PulseAudio player."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with patch.object(audio_feedback.shutil, "which") as mock_which:
# Mock shutil.which to return True for paplay and False for others
def which_side_effect(cmd):
return cmd == "paplay"
mock_which.side_effect = which_side_effect
# Call the function
player, formats = audio_feedback._get_audio_player()
# Verify the correct player was detected
self.assertEqual(player, "paplay")
self.assertEqual(formats, ["wav"])
def test_get_audio_player_alsa(self):
"""Test detecting ALSA player."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with patch.object(audio_feedback.shutil, "which") as mock_which:
# Mock shutil.which to return False for paplay, True for aplay
def which_side_effect(cmd):
return {
"paplay": False,
"aplay": True,
"play": False,
"mplayer": False,
}.get(cmd, False)
mock_which.side_effect = which_side_effect
# Call the function
player, formats = audio_feedback._get_audio_player()
# Verify the correct player was detected
self.assertEqual(player, "aplay")
self.assertEqual(formats, ["wav"])
def test_get_audio_player_sox(self):
"""Test detecting SoX player."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with patch.object(audio_feedback.shutil, "which") as mock_which:
# Mock shutil.which to return False for paplay/aplay, True for play
def which_side_effect(cmd):
return {
"paplay": False,
"aplay": False,
"play": True,
"mplayer": False,
}.get(cmd, False)
mock_which.side_effect = which_side_effect
# Call the function
player, formats = audio_feedback._get_audio_player()
# Verify the correct player was detected
self.assertEqual(player, "play")
self.assertEqual(formats, ["wav"])
def test_get_audio_player_mplayer(self):
"""Test detecting MPlayer."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with patch.object(audio_feedback.shutil, "which") as mock_which:
# Mock shutil.which to return False for all except mplayer
def which_side_effect(cmd):
return {
"paplay": False,
"aplay": False,
"play": False,
"mplayer": True,
}.get(cmd, False)
mock_which.side_effect = which_side_effect
# Call the function
player, formats = audio_feedback._get_audio_player()
# Verify the correct player was detected
self.assertEqual(player, "mplayer")
self.assertEqual(formats, ["wav"])
def test_get_audio_player_none(self):
"""Test behavior when no audio player is available."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with patch.object(audio_feedback.shutil, "which", return_value=None):
# Call the function
player, formats = audio_feedback._get_audio_player()
# Verify no player was detected
self.assertIsNone(player)
self.assertEqual(formats, [])
def test_play_sound_file_missing(self):
"""Test playing a missing sound file."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with patch.object(audio_feedback.os.path, "exists", return_value=False):
# Call the function
result = audio_feedback._play_sound_file("nonexistent.wav")
# Verify the function returned False
self.assertFalse(result)
def test_play_sound_file_no_player(self):
"""Test playing sound with no available player."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with (
patch.object(audio_feedback.os.path, "exists", return_value=True),
patch.object(audio_feedback, "_get_audio_player", return_value=(None, [])),
):
# Call the function
result = audio_feedback._play_sound_file("test.wav")
# Verify the function returned False
self.assertFalse(result)
def test_play_sound_file_paplay(self):
"""Test playing sound with paplay."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with tempfile.TemporaryDirectory() as tmp:
cue = _write_test_wav(os.path.join(tmp, "cue.wav"))
with (
patch.object(
audio_feedback,
"_get_audio_player",
return_value=("paplay", ["wav"]),
),
patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
):
result = audio_feedback._play_sound_file(cue)
self.assertTrue(result)
mock_popen.assert_called_once()
played = _assert_played_wav(_popen_argv(mock_popen), "paplay")
self.assertTrue(os.path.isfile(played), played)
def test_play_sound_file_aplay(self):
"""Test playing sound with aplay."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with tempfile.TemporaryDirectory() as tmp:
cue = _write_test_wav(os.path.join(tmp, "cue.wav"))
with (
patch.object(
audio_feedback,
"_get_audio_player",
return_value=("aplay", ["wav"]),
),
patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
):
result = audio_feedback._play_sound_file(cue)
self.assertTrue(result)
mock_popen.assert_called_once()
played = _assert_played_wav(_popen_argv(mock_popen), "aplay", ("-q",))
self.assertTrue(os.path.isfile(played), played)
def test_play_sound_file_mplayer(self):
"""Test playing sound with mplayer."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with tempfile.TemporaryDirectory() as tmp:
cue = _write_test_wav(os.path.join(tmp, "cue.wav"))
with (
patch.object(
audio_feedback,
"_get_audio_player",
return_value=("mplayer", ["wav"]),
),
patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
):
result = audio_feedback._play_sound_file(cue)
self.assertTrue(result)
mock_popen.assert_called_once()
played = _assert_played_wav(_popen_argv(mock_popen), "mplayer", ("-really-quiet",))
self.assertTrue(os.path.isfile(played), played)
def test_play_sound_file_play(self):
"""Test playing sound with play (SoX)."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with tempfile.TemporaryDirectory() as tmp:
cue = _write_test_wav(os.path.join(tmp, "cue.wav"))
with (
patch.object(audio_feedback, "_get_audio_player", return_value=("play", ["wav"])),
patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
):
result = audio_feedback._play_sound_file(cue)
self.assertTrue(result)
mock_popen.assert_called_once()
played = _assert_played_wav(_popen_argv(mock_popen), "play", ("-q",))
self.assertTrue(os.path.isfile(played), played)
def test_play_sound_file_exception(self):
"""Test handling exception when playing sound."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with (
patch.object(audio_feedback.os.path, "exists", return_value=True),
patch.object(
audio_feedback,
"_get_audio_player",
return_value=("paplay", ["wav"]),
),
patch.object(
audio_feedback.subprocess,
"Popen",
side_effect=Exception("Mock error"),
),
):
# Call the function
result = audio_feedback._play_sound_file("test.wav")
# Verify the function returned False
self.assertFalse(result)
def test_play_sound_file_prepends_silent_preroll(self):
"""Real players play a WAV with leading silence of the preroll duration."""
import vocalinux.ui.audio_feedback as audio_feedback
framerate = 8000
nchannels = 1
sampwidth = 2
nframes = 80
payload = b"\x01\x00" * nframes
with tempfile.TemporaryDirectory() as tmp:
cue = _write_test_wav(
os.path.join(tmp, "cue.wav"),
nframes=nframes,
framerate=framerate,
nchannels=nchannels,
sampwidth=sampwidth,
payload=payload,
)
with (
patch.object(
audio_feedback,
"_get_audio_player",
return_value=("paplay", ["wav"]),
),
patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
):
result = audio_feedback._play_sound_file(cue)
self.assertTrue(result)
mock_popen.assert_called_once()
played = _assert_played_wav(_popen_argv(mock_popen), "paplay")
self.assertTrue(os.path.isfile(played), played)
self.assertNotEqual(os.path.realpath(played), os.path.realpath(cue))
with wave.open(cue, "rb") as src, wave.open(played, "rb") as dst:
self.assertEqual(dst.getnchannels(), nchannels)
self.assertEqual(dst.getsampwidth(), sampwidth)
self.assertEqual(dst.getframerate(), framerate)
src_frames = src.getnframes()
dst_frames = dst.getnframes()
dst_bytes = dst.readframes(dst_frames)
preroll_ms = audio_feedback._SINK_WAKE_PREROLL_MS
expected_silence = max(1, int(round(framerate * preroll_ms / 1000.0)))
self.assertEqual(dst_frames, src_frames + expected_silence)
frame_size = nchannels * sampwidth
silence = dst_bytes[: expected_silence * frame_size]
rest = dst_bytes[expected_silence * frame_size :]
self.assertEqual(silence, b"\x00" * len(silence))
self.assertEqual(rest, payload)
def test_preroll_cache_reuses_built_file(self):
"""The same source path and mtime reuse the prerolled WAV."""
import vocalinux.ui.audio_feedback as audio_feedback
with tempfile.TemporaryDirectory() as tmp:
cue = _write_test_wav(os.path.join(tmp, "cue.wav"))
with (
patch.object(
audio_feedback,
"_get_audio_player",
return_value=("paplay", ["wav"]),
),
patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
):
self.assertTrue(audio_feedback._play_sound_file(cue))
self.assertTrue(audio_feedback._play_sound_file(cue))
self.assertEqual(mock_popen.call_count, 2)
first = _assert_played_wav(list(mock_popen.call_args_list[0].args[0]), "paplay")
second = _assert_played_wav(list(mock_popen.call_args_list[1].args[0]), "paplay")
self.assertEqual(first, second)
def test_preroll_build_failure_falls_back_to_original(self):
"""A preroll build error plays the original cue instead of failing closed."""
import vocalinux.ui.audio_feedback as audio_feedback
with tempfile.TemporaryDirectory() as tmp:
cue = _write_test_wav(os.path.join(tmp, "cue.wav"))
with (
patch.object(
audio_feedback,
"_get_audio_player",
return_value=("paplay", ["wav"]),
),
patch.object(audio_feedback.wave, "open", side_effect=wave.Error("boom")),
patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
):
result = audio_feedback._play_sound_file(cue)
self.assertTrue(result)
played = _assert_played_wav(_popen_argv(mock_popen), "paplay")
self.assertEqual(os.path.realpath(played), os.path.realpath(cue))
def test_preroll_eoferror_falls_back_to_original(self):
"""A truncated/corrupt WAV raising EOFError still plays the original cue."""
import vocalinux.ui.audio_feedback as audio_feedback
with tempfile.TemporaryDirectory() as tmp:
cue = os.path.join(tmp, "cue.wav")
with open(cue, "wb") as handle:
handle.write(b"RIFF")
with (
patch.object(
audio_feedback,
"_get_audio_player",
return_value=("paplay", ["wav"]),
),
patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
):
result = audio_feedback._play_sound_file(cue)
self.assertTrue(result)
played = _assert_played_wav(_popen_argv(mock_popen), "paplay")
self.assertEqual(os.path.realpath(played), os.path.realpath(cue))
def test_play_start_sound(self):
"""Test playing start sound (default tone is voca)."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with (
patch.object(audio_feedback, "_is_sound_effects_enabled", return_value=True),
patch.object(audio_feedback, "_resolved_tone", return_value="voca"),
patch.object(audio_feedback, "_play_sound_file") as mock_play,
):
audio_feedback.play_start_sound()
mock_play.assert_called_once_with(audio_feedback.tone_sound_path("voca", "start"))
def test_play_stop_sound(self):
"""Test playing stop sound (default tone is voca)."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with (
patch.object(audio_feedback, "_is_sound_effects_enabled", return_value=True),
patch.object(audio_feedback, "_resolved_tone", return_value="voca"),
patch.object(audio_feedback, "_play_sound_file") as mock_play,
):
audio_feedback.play_stop_sound()
mock_play.assert_called_once_with(audio_feedback.tone_sound_path("voca", "stop"))
def test_play_error_sound(self):
"""Test playing error sound."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with patch.object(audio_feedback, "_play_sound_file") as mock_play:
# Call the function
audio_feedback.play_error_sound()
# Verify _play_sound_file was called with correct path
mock_play.assert_called_once_with(audio_feedback.ERROR_SOUND)
def test_play_sound_file_ci_test_player(self):
"""Test playing sound with ci_test_player (GitHub Actions fallback)."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with tempfile.TemporaryDirectory() as tmp:
cue = _write_test_wav(os.path.join(tmp, "cue.wav"))
with (
patch.object(
audio_feedback,
"_get_audio_player",
return_value=("ci_test_player", ["wav"]),
),
patch.object(audio_feedback.subprocess, "Popen") as mock_popen,
):
result = audio_feedback._play_sound_file(cue)
self.assertTrue(result)
mock_popen.assert_called_once()
argv = _popen_argv(mock_popen)
self.assertEqual(argv[0], "ci_test_player")
self.assertEqual(argv[1], cue)
def test_get_audio_player_github_actions_fallback(self):
"""Test ci_test_player assignment in GitHub Actions without audio player."""
# Import the module first
import vocalinux.ui.audio_feedback as audio_feedback
with (
patch.object(audio_feedback.shutil, "which", return_value=None),
patch.object(audio_feedback.os.path, "exists", return_value=True),
patch.dict("os.environ", {"GITHUB_ACTIONS": "true"}),
):
# First verify _get_audio_player returns None
player, formats = audio_feedback._get_audio_player()
self.assertIsNone(player)
# Now test _play_sound_file which should assign ci_test_player
with patch.object(audio_feedback.subprocess, "Popen") as mock_popen:
result = audio_feedback._play_sound_file("test.wav")
# Should have used ci_test_player
self.assertTrue(result)
mock_popen.assert_called_once()
args, _ = mock_popen.call_args
self.assertEqual(args[0][0], "ci_test_player")
def test_play_start_sound_when_disabled(self):
"""Test that start sound is not played when sound effects are disabled."""
import vocalinux.ui.audio_feedback as audio_feedback
with patch.object(audio_feedback, "_is_sound_effects_enabled", return_value=False):
result = audio_feedback.play_start_sound()
self.assertFalse(result)
def test_play_stop_sound_when_disabled(self):
"""Test that stop sound is not played when sound effects are disabled."""
import vocalinux.ui.audio_feedback as audio_feedback
with patch.object(audio_feedback, "_is_sound_effects_enabled", return_value=False):
result = audio_feedback.play_stop_sound()
self.assertFalse(result)
def test_play_error_sound_when_disabled(self):
"""Test that error sound is not played when sound effects are disabled."""
import vocalinux.ui.audio_feedback as audio_feedback
with patch.object(audio_feedback, "_is_sound_effects_enabled", return_value=False):
result = audio_feedback.play_error_sound()
self.assertFalse(result)
def test_is_sound_effects_enabled_returns_true_on_error(self):
"""Test that sound effects are enabled by default when config is unavailable."""
import vocalinux.ui.audio_feedback as audio_feedback
with patch(
"vocalinux.ui.config_manager.get_shared_config_manager",
side_effect=Exception("No config"),
):
result = audio_feedback._is_sound_effects_enabled()
self.assertTrue(result)
def test_missing_or_unknown_tone_uses_voca_not_legacy_pair(self):
"""Unset or unknown tone ids play voca, not start_recording/stop_recording."""
import vocalinux.ui.audio_feedback as audio_feedback
from vocalinux.ui.config_manager import (
DEFAULT_SOUND_EFFECT_TONE,
normalize_sound_effect_tone,
)
self.assertEqual(DEFAULT_SOUND_EFFECT_TONE, "voca")
for raw in (None, "", "fifth", "01-linux-glide", "not-a-tone"):
self.assertEqual(normalize_sound_effect_tone(raw), "voca")
with (
patch.object(audio_feedback, "_is_sound_effects_enabled", return_value=True),
patch.object(audio_feedback, "_resolved_tone", return_value="voca"),
patch.object(audio_feedback, "_play_sound_file") as mock_play,
):
audio_feedback.play_start_sound()
audio_feedback.play_stop_sound()
played = [call.args[0] for call in mock_play.call_args_list]
self.assertTrue(played[0].endswith("voca_start.wav"))
self.assertTrue(played[1].endswith("voca_stop.wav"))
self.assertFalse(any("start_recording.wav" in path for path in played))
def test_off_plays_nothing_for_start_stop(self):
import vocalinux.ui.audio_feedback as audio_feedback
with (
patch.object(audio_feedback, "_is_sound_effects_enabled", return_value=True),
patch.object(audio_feedback, "_resolved_tone", return_value="off"),
patch.object(audio_feedback, "_play_sound_file") as mock_play,
):
self.assertFalse(audio_feedback.play_start_sound())
self.assertFalse(audio_feedback.play_stop_sound())
mock_play.assert_not_called()
with (
patch.object(audio_feedback, "_is_sound_effects_enabled", return_value=True),
patch.object(audio_feedback, "_play_sound_file") as mock_play,
):
self.assertTrue(audio_feedback.play_error_sound())
mock_play.assert_called_once_with(audio_feedback.ERROR_SOUND)
def test_each_catalog_id_maps_to_its_pair(self):
import vocalinux.ui.audio_feedback as audio_feedback
from vocalinux.ui.config_manager import SOUND_EFFECT_TONE_IDS, normalize_sound_effect_tone
catalog_ids = (
"lift",
"flick",
"ember",
"step",
"voca",
"soft",
"chirp",
"scale",
"drop",
"glass",
"off",
)
self.assertEqual(set(catalog_ids), set(SOUND_EFFECT_TONE_IDS))
self.assertNotIn("fifth", SOUND_EFFECT_TONE_IDS)
for tone_id in catalog_ids:
self.assertEqual(normalize_sound_effect_tone(tone_id), tone_id)
if tone_id == "off":
continue
start = audio_feedback.tone_sound_path(tone_id, "start")
stop = audio_feedback.tone_sound_path(tone_id, "stop")
self.assertTrue(os.path.isfile(start), start)
self.assertTrue(os.path.isfile(stop), stop)
self.assertTrue(start.endswith(f"{tone_id}_start.wav"))
self.assertTrue(stop.endswith(f"{tone_id}_stop.wav"))
with (
patch.object(audio_feedback, "_is_sound_effects_enabled", return_value=True),
patch.object(audio_feedback, "_resolved_tone", return_value=tone_id),
patch.object(audio_feedback, "_play_sound_file") as mock_play,
):
audio_feedback.play_start_sound()
audio_feedback.play_stop_sound()
self.assertEqual(mock_play.call_args_list[0].args[0], start)
self.assertEqual(mock_play.call_args_list[1].args[0], stop)
def test_preview_tone_does_not_crash(self):
import vocalinux.ui.audio_feedback as audio_feedback
class ImmediateTimer:
def __init__(self, delay, function, args=None, kwargs=None):
self.function = function
self.args = args or ()
self.kwargs = kwargs or {}
def start(self):
self.function(*self.args, **self.kwargs)
with (
patch.object(audio_feedback, "_play_sound_file", return_value=True) as mock_play,
patch.object(audio_feedback.threading, "Timer", ImmediateTimer),
):
self.assertTrue(audio_feedback.preview_tone("voca"))
self.assertEqual(len(mock_play.call_args_list), 2)
self.assertTrue(mock_play.call_args_list[0].args[0].endswith("voca_start.wav"))
self.assertTrue(mock_play.call_args_list[1].args[0].endswith("voca_stop.wav"))
mock_play.reset_mock()
self.assertFalse(audio_feedback.preview_tone("off"))
mock_play.assert_not_called()
mock_play.reset_mock()
self.assertTrue(audio_feedback.preview_tone("not-a-tone"))
self.assertTrue(mock_play.call_args_list[0].args[0].endswith("voca_start.wav"))
def test_preview_tone_cue_plays_one_side(self):
import vocalinux.ui.audio_feedback as audio_feedback
with patch.object(audio_feedback, "_play_sound_file", return_value=True) as mock_play:
self.assertTrue(audio_feedback.preview_tone_cue("voca", "start"))
self.assertTrue(mock_play.call_args_list[0].args[0].endswith("voca_start.wav"))
mock_play.reset_mock()
self.assertTrue(audio_feedback.preview_tone_cue("voca", "stop"))
self.assertTrue(mock_play.call_args_list[0].args[0].endswith("voca_stop.wav"))
mock_play.reset_mock()
self.assertFalse(audio_feedback.preview_tone_cue("off", "start"))
mock_play.assert_not_called()