"""Tests for the audio.cpp backend setup module (backends/audiocpp.py).""" import argparse import hashlib import io import json import shutil import sys import tarfile import tempfile import threading import unittest import zipfile from contextlib import redirect_stdout from pathlib import Path from typing import Optional from unittest.mock import MagicMock, patch from converter import config from backends import audiocpp as make_server import os from backends import common, servers from ui import taskview from ui import tui FAKE_CONFIG = ( 'LANGUAGE = "English"\n' "\n" 'AUDIOCPP_API_URL = "http://127.0.0.1:9999" # audio.cpp audiocpp_server\n' "\n" "CHUNK_SIZE = 250\n" ) def _write_spec(checkout: Path, family: str, *, display_name=None, tasks=("tts", "clone"), languages=("en",), packages=None, category="tts"): """Write a minimal model_specs/.json into a fake checkout.""" specs = checkout / "model_specs" specs.mkdir(parents=True, exist_ok=True) if packages is None: packages = [{ "id": f"{family}_q8_0", "default": True, "format": "gguf", "target_directory": f"{family}-GGUF", }] spec = { "family": family, "display_name": display_name or family, "category": category, "tasks": list(tasks), "languages": list(languages), "packages": packages, } (specs / f"{family}.json").write_text(json.dumps(spec), encoding="utf-8") return spec def _make_checkout(tmp: Path) -> Path: """Create a fake audio.cpp checkout with a realistic model_specs set.""" checkout = tmp / "audio.cpp" checkout.mkdir() _write_spec(checkout, "qwen3_tts", display_name="Qwen3-TTS", tasks=("tts", "clone", "design"), languages=("zh", "en", "ja"), packages=[ {"id": "qwen3_tts_1_7b_base_q8_0", "default": True, "format": "gguf", "target_directory": "Qwen3-TTS-12Hz-1.7B-Base-GGUF"}, {"id": "qwen3_tts_1_7b_customvoice_q8_0", "format": "gguf", "target_directory": "Qwen3-TTS-12Hz-1.7B-CustomVoice-GGUF"}, {"id": "qwen3_tts_1_7b_voicedesign_q8_0", "format": "gguf", "target_directory": "Qwen3-TTS-12Hz-1.7B-VoiceDesign-GGUF"}, ]) _write_spec(checkout, "higgs_audio_tts", display_name="Higgs Audio v3 TTS 4B", languages=("auto",), packages=[{ "id": "higgs_audio_tts_4b_q8_0", "default": True, "format": "gguf", "target_directory": "Higgs-Audio-v3-TTS-4B-GGUF", }]) _write_spec(checkout, "voxcpm2", display_name="VoxCPM2-2B", languages=("en", "zh"), packages=[{ "id": "voxcpm2_q8_0", "default": True, "format": "gguf", "target_directory": "VoxCPM2-GGUF", }]) _write_spec(checkout, "index_tts2", display_name="IndexTTS-2", languages=("zh", "en"), packages=[{ "id": "index_tts2_q8_0", "default": True, "format": "gguf", "target_directory": "IndexTTS2-GGUF", }]) _write_spec(checkout, "pocket_tts", display_name="PocketTTS-100M", tasks=("tts", "clone"), languages=("en", "de"), packages=[{ "id": "pocket_tts_q8_0", "default": True, "format": "gguf", "target_directory": "PocketTTS-GGUF", }]) _write_spec(checkout, "supertonic", display_name="Supertonic 3", tasks=("tts",), languages=("en", "ko"), packages=[{ "id": "supertonic_q8_0", "default": True, "format": "gguf", "target_directory": "Supertonic-GGUF", }]) # An ASR family that must be filtered out. _write_spec(checkout, "qwen3_asr", display_name="Qwen3-ASR", tasks=("asr",), category="asr") # A TTS family with no installable packages (must be skipped). _write_spec(checkout, "empty_tts", display_name="Empty TTS", tasks=("tts",), packages=[]) return checkout class FindWavFilesTests(unittest.TestCase): def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.folder = Path(self._tmp.name) def tearDown(self): self._tmp.cleanup() def _touch(self, name): path = self.folder / name path.write_bytes(b"x") return path def test_finds_only_wavs_case_insensitive(self): self._touch("b.wav") self._touch("a.WAV") self._touch("notes.txt") (self.folder / "sub").mkdir() (self.folder / "sub" / "c.wav").write_bytes(b"x") names = [path.name for path in common.find_wav_files(self.folder)] self.assertEqual(names, ["a.WAV", "b.wav"]) def test_sorted_alphabetically_case_insensitive(self): for name in ("Zed.wav", "alpha.wav", "Beta.wav"): self._touch(name) names = [path.name for path in common.find_wav_files(self.folder)] self.assertEqual(names, ["alpha.wav", "Beta.wav", "Zed.wav"]) def test_empty_directory_returns_empty_list(self): self.assertEqual(common.find_wav_files(self.folder), []) class DetectWavDirTests(unittest.TestCase): """Shallow .wav-directory discovery across the two checkout roots.""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.root = Path(self._td.name) self.audiocpp = self.root / "audio.cpp" self.tts_root = self.root / "tts-audiobook-generator" self.audiocpp.mkdir() self.tts_root.mkdir() def tearDown(self): self._td.cleanup() def _wav_dir(self, where, name="voices"): directory = where / name directory.mkdir(parents=True, exist_ok=True) (directory / "voice.wav").write_bytes(b"x") return directory def test_unique_wav_dir_in_tts_root_returned(self): found = self._wav_dir(self.tts_root, "voices") self.assertEqual(common.detect_wav_dir(self.audiocpp, self.tts_root), found) def test_unique_wav_dir_in_audiocpp_root_returned(self): found = self._wav_dir(self.audiocpp, "reference") self.assertEqual(common.detect_wav_dir(self.audiocpp, self.tts_root), found) def test_root_itself_containing_wavs_returned(self): (self.tts_root / "direct.wav").write_bytes(b"x") self.assertEqual(common.detect_wav_dir(self.audiocpp, self.tts_root), self.tts_root) def test_multiple_wav_dirs_returns_none(self): self._wav_dir(self.tts_root, "one") self._wav_dir(self.audiocpp, "two") self.assertIsNone(common.detect_wav_dir(self.audiocpp, self.tts_root)) def test_output_dir_of_tts_root_excluded(self): self._wav_dir(self.tts_root, "output") self.assertIsNone(common.detect_wav_dir(self.audiocpp, self.tts_root)) def test_no_wavs_returns_none(self): self.assertIsNone(common.detect_wav_dir(self.audiocpp, self.tts_root)) def test_nested_wav_dir_not_seen(self): nested = self.tts_root / "outer" / "inner" nested.mkdir(parents=True) (nested / "voice.wav").write_bytes(b"x") self.assertIsNone(common.detect_wav_dir(self.audiocpp, self.tts_root)) class ConfigPortTests(unittest.TestCase): def test_port_parsed_from_config_url(self): with patch.object(config, "AUDIOCPP_API_URL", "http://127.0.0.1:8080"): self.assertEqual(make_server.configsync.config_port(), 8080) def test_missing_port_falls_back(self): with patch.object(config, "AUDIOCPP_API_URL", "http://127.0.0.1"): self.assertEqual(make_server.configsync.config_port(), make_server.FALLBACK_PORT) def test_invalid_url_falls_back(self): with patch.object(config, "AUDIOCPP_API_URL", "not a url"): self.assertEqual(make_server.configsync.config_port(), make_server.FALLBACK_PORT) def test_url_with_port_replaces_port(self): # audiocpp reuses the shared helper (backends.common.url_with_port). self.assertEqual( common.url_with_port("http://127.0.0.1:8080", 9000), "http://127.0.0.1:9000") def test_url_without_port_adds_port(self): self.assertEqual( common.url_with_port("http://localhost", 8080), "http://localhost:8080") class UpdateConfigPortTests(unittest.TestCase): def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.config_path = Path(self._tmp.name) / "config.py" self.config_path.write_text(FAKE_CONFIG, encoding="utf-8") # The shared helper also mirrors values onto converter.config. self._saved_url = config.AUDIOCPP_API_URL def tearDown(self): config.AUDIOCPP_API_URL = self._saved_url self._tmp.cleanup() def test_rewrites_port_preserving_comment(self): changed = make_server.configsync.update_config_api_url_port( 8080, config_path=self.config_path) self.assertTrue(changed) text = self.config_path.read_text(encoding="utf-8") self.assertIn( 'AUDIOCPP_API_URL = "http://127.0.0.1:8080" # audio.cpp audiocpp_server', text) self.assertIn('LANGUAGE = "English"', text) self.assertIn("CHUNK_SIZE = 250", text) def test_returns_false_when_no_url_line(self): path = Path(self._tmp.name) / "other.py" path.write_text('CHUNK_SIZE = 250\n', encoding="utf-8") self.assertFalse(make_server.configsync.update_config_api_url_port( 8080, config_path=path)) def test_port_unchanged_is_a_success_noop(self): # The file already holds the port: success, nothing rewritten. self.assertTrue(make_server.configsync.update_config_api_url_port( 9999, config_path=self.config_path)) self.assertEqual(self.config_path.read_text(encoding="utf-8"), FAKE_CONFIG) def test_returns_false_when_file_missing(self): self.assertFalse(make_server.configsync.update_config_api_url_port( 8080, config_path=Path(self._tmp.name) / "nope.py")) class ResolveWavDirArgTests(unittest.TestCase): def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.folder = Path(self._tmp.name) def tearDown(self): self._tmp.cleanup() def test_resolves_to_absolute(self): self.assertEqual(common.resolve_wav_dir_arg(str(self.folder)), self.folder.resolve()) def test_strips_surrounding_quotes(self): quoted = f'"{self.folder}"' self.assertEqual(common.resolve_wav_dir_arg(quoted), self.folder.resolve()) def test_strips_single_quotes(self): quoted = f"'{self.folder}'" self.assertEqual(common.resolve_wav_dir_arg(quoted), self.folder.resolve()) def test_strips_whitespace(self): self.assertEqual(common.resolve_wav_dir_arg(f" {self.folder} "), self.folder.resolve()) def test_expands_tilde(self): with patch.object(os.path, "expanduser", return_value=str(self.folder)) as mock_expand: result = common.resolve_wav_dir_arg("~/voices") mock_expand.assert_called_once_with("~/voices") self.assertEqual(result, self.folder.resolve()) def test_trailing_slash_preserved_as_dir(self): self.assertEqual(common.resolve_wav_dir_arg(f"{self.folder}/"), self.folder.resolve()) class NormalizeDirArgTests(unittest.TestCase): """Path normalization for user-supplied directory arguments.""" def test_expands_tilde_and_resolves(self): with patch.object(common.os.path, "expanduser", return_value="/home/u/audio.cpp") as mock_expand: result = common.normalize_dir_arg("~/audio.cpp") mock_expand.assert_called_once_with("~/audio.cpp") self.assertEqual(result, Path("/home/u/audio.cpp").resolve()) def test_strips_quotes_and_whitespace(self): with patch.object(common.os.path, "expanduser", side_effect=lambda s: s): result = common.normalize_dir_arg(' "/tmp/foo" ') self.assertEqual(result, Path("/tmp/foo").resolve()) class FindLocalCheckoutTests(unittest.TestCase): """find_local_checkout resolves ./app/audio.cpp and nothing else.""" def test_none_when_no_checkout_in_app_dir(self): with tempfile.TemporaryDirectory() as td, \ patch.object(make_server.build, "APP_DIR", Path(td)): self.assertIsNone(make_server.build.find_local_checkout()) def test_returns_the_managed_checkout(self): with tempfile.TemporaryDirectory() as td, \ patch.object(make_server.build, "APP_DIR", Path(td)): checkout = _make_checkout(Path(td)) self.assertEqual(make_server.build.find_local_checkout(), checkout) def test_none_when_checkout_lacks_model_specs(self): with tempfile.TemporaryDirectory() as td, \ patch.object(make_server.build, "APP_DIR", Path(td)): (Path(td) / "audio.cpp").mkdir() self.assertIsNone(make_server.build.find_local_checkout()) def _add_options_to_spec(checkout: Path, family: str, *, options=None) -> None: """Rewrite one family spec with an (optional) options block.""" path = checkout / "model_specs" / f"{family}.json" spec = json.loads(path.read_text(encoding="utf-8")) if options is not None: spec["options"] = options elif "options" in spec: del spec["options"] path.write_text(json.dumps(spec), encoding="utf-8") class LoadModelCatalogTests(unittest.TestCase): def setUp(self): self._td = tempfile.TemporaryDirectory() self.checkout = _make_checkout(Path(self._td.name)) def tearDown(self): self._td.cleanup() def test_includes_tts_families_excludes_asr(self): catalog = make_server.catalog.load_model_catalog(self.checkout) families = [entry["family"] for entry in catalog] self.assertIn("qwen3_tts", families) self.assertIn("higgs_audio_tts", families) self.assertIn("pocket_tts", families) self.assertIn("supertonic", families) self.assertNotIn("qwen3_asr", families) def test_skips_families_with_no_packages(self): catalog = make_server.catalog.load_model_catalog(self.checkout) self.assertNotIn("empty_tts", [entry["family"] for entry in catalog]) def test_skips_speech_to_speech_only_families(self): # PersonaPlex-style specs: categorized "tts" but with a task list # naming no text-synthesis capability — the family cannot narrate # text and every request would fail, so it is never offered for # install (the generic task check, no family names hardcoded). _write_spec(self.checkout, "personaplex", tasks=("s2s",)) catalog = make_server.catalog.load_model_catalog(self.checkout) self.assertNotIn("personaplex", [entry["family"] for entry in catalog]) def test_design_only_family_is_kept(self): # Voice design is a text-synthesis task: a vdes-only spec stays # installable. _write_spec(self.checkout, "designer", tasks=("vdes",)) catalog = make_server.catalog.load_model_catalog(self.checkout) self.assertIn("designer", [entry["family"] for entry in catalog]) def test_taskless_tts_category_fallback_is_kept(self): # A spec without a task list keeps the old category fallback, so # a future/malformed "tts" spec is not silently dropped. _write_spec(self.checkout, "mystery_tts", tasks=()) catalog = make_server.catalog.load_model_catalog(self.checkout) self.assertIn("mystery_tts", [entry["family"] for entry in catalog]) def test_families_sorted_alphabetically_by_display_name(self): catalog = make_server.catalog.load_model_catalog(self.checkout) names = [entry["display_name"].lower() for entry in catalog] self.assertEqual(names, sorted(names)) self.assertNotIn("tested", catalog[0]) self.assertNotIn("TESTED_FAMILIES", dir(make_server)) def test_default_package_and_target_directory_resolved(self): catalog = make_server.catalog.load_model_catalog(self.checkout) by_family = {entry["family"]: entry for entry in catalog} higgs = by_family["higgs_audio_tts"] self.assertEqual(higgs["install_id"], "higgs_audio_tts_4b_q8_0") self.assertEqual(higgs["default_path"], "models/Higgs-Audio-v3-TTS-4B-GGUF") def test_picks_first_gguf_when_no_default_flag(self): _write_spec(self.checkout, "voxcpm2", display_name="VoxCPM2-2B", packages=[ {"id": "voxcpm2_bf16", "format": "gguf", "target_directory": "VoxCPM2-GGUF"}, {"id": "voxcpm2_q8_0", "format": "gguf", "target_directory": "VoxCPM2-GGUF"}, ]) catalog = make_server.catalog.load_model_catalog(self.checkout) by_family = {entry["family"]: entry for entry in catalog} self.assertEqual(by_family["voxcpm2"]["install_id"], "voxcpm2_bf16") def test_clone_capability_from_tasks(self): catalog = make_server.catalog.load_model_catalog(self.checkout) by_family = {entry["family"]: entry for entry in catalog} self.assertTrue(by_family["higgs_audio_tts"]["clone_capable"]) self.assertFalse(by_family["supertonic"]["clone_capable"]) def test_missing_model_specs_dir_raises(self): empty = Path(self._td.name) / "empty" empty.mkdir() with self.assertRaises(NotADirectoryError): make_server.catalog.load_model_catalog(empty) class RequestOptionsFamiliesTests(unittest.TestCase): """request_options_families: which specs declare request options.""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.checkout = _make_checkout(Path(self._td.name)) _add_options_to_spec( self.checkout, "higgs_audio_tts", options={"request": [{"id": "temperature", "default": 0.8}, {"id": "speed"}]}) def tearDown(self): self._td.cleanup() def test_family_with_request_options_listed_with_display_name(self): families = make_server.request_options_families(self.checkout) self.assertEqual(families.get("higgs_audio_tts"), {"display_name": "Higgs Audio v3 TTS 4B"}) def test_family_without_options_block_absent(self): families = make_server.request_options_families(self.checkout) self.assertNotIn("qwen3_tts", families) self.assertNotIn("voxcpm2", families) def test_empty_request_list_does_not_count_as_support(self): _add_options_to_spec(self.checkout, "supertonic", options={"request": []}) families = make_server.request_options_families(self.checkout) self.assertNotIn("supertonic", families) def test_missing_specs_dir_yields_empty_map(self): self.assertEqual(make_server.request_options_families( Path(self._td.name)), {}) def test_unparsable_spec_skipped(self): (self.checkout / "model_specs" / "broken.json").write_text( "{not json", encoding="utf-8") families = make_server.request_options_families(self.checkout) self.assertNotIn("broken", families) self.assertIn("higgs_audio_tts", families) class SupportsRequestOptionsTests(unittest.TestCase): """supports_request_options: True / False / unknown tri-state.""" FAMILIES = {"higgs_audio_tts": {"display_name": "Higgs"}} def test_true_only_for_a_listed_family(self): self.assertTrue(make_server.supports_request_options( self.FAMILIES, "higgs_audio_tts")) def test_false_for_a_read_but_unlisted_family(self): self.assertFalse(make_server.supports_request_options( self.FAMILIES, "qwen3_tts")) def test_none_when_no_local_specs_exist(self): self.assertIsNone(make_server.supports_request_options({}, "any")) # An entry with no family at all is unclassifiable too. self.assertIsNone(make_server.supports_request_options({}, "")) class DetectBackendTests(unittest.TestCase): """Backend detection from audio.cpp build directory names.""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.checkout = Path(self._td.name) / "audio.cpp" self.checkout.mkdir() def tearDown(self): self._td.cleanup() def _build(self, name, binary="audiocpp_server"): build_dir = self.checkout / "build" / name bin_dir = build_dir / "bin" bin_dir.mkdir(parents=True) (bin_dir / binary).write_bytes(b"x") return build_dir def test_no_build_dir_returns_none(self): self.assertIsNone(make_server.catalog.detect_backend(self.checkout)) def test_unique_linux_backend_detected(self): self._build("linux-cuda-release") self.assertEqual(make_server.catalog.detect_backend(self.checkout), "cuda") def test_windows_exe_backend_detected(self): self._build("windows-vulkan-debug", binary="audiocpp_server.exe") self.assertEqual(make_server.catalog.detect_backend(self.checkout), "vulkan") def test_hip_backend_detected(self): self._build("linux-hip-release") self.assertEqual(make_server.catalog.detect_backend(self.checkout), "hip") def test_cpu_backend_detected(self): self._build("linux-cpu-release") self.assertEqual(make_server.catalog.detect_backend(self.checkout), "cpu") def test_metal_maps_to_cpu(self): self._build("macos-metal-release") self.assertEqual(make_server.catalog.detect_backend(self.checkout), "cpu") def test_multiple_backends_returns_none(self): self._build("linux-cuda-release") self._build("linux-cpu-release") self.assertIsNone(make_server.catalog.detect_backend(self.checkout)) def test_multiple_builds_same_backend_detected(self): self._build("linux-cuda-release") self._build("windows-cuda-debug") self.assertEqual(make_server.catalog.detect_backend(self.checkout), "cuda") def test_build_dir_without_binary_ignored(self): (self.checkout / "build" / "linux-cuda-release").mkdir(parents=True) self.assertIsNone(make_server.catalog.detect_backend(self.checkout)) def test_non_matching_build_dir_name_ignored(self): self._build("linux-mybuild-release") self.assertIsNone(make_server.catalog.detect_backend(self.checkout)) class BackendOptionsTests(unittest.TestCase): """Aligned backend menu labels and the [auto-detected] default.""" def test_options_have_aligned_dashes(self): options, default_index = make_server.catalog._backend_options() dash_columns = {label.index(" - ") for label, _ in options} self.assertEqual(len(dash_columns), 1) self.assertEqual(default_index, 0) def test_detected_backend_marked_and_defaulted(self): options, default_index = make_server.catalog._backend_options("vulkan") labels = [label for label, _ in options] self.assertEqual(default_index, labels.index(next( label for label, value in options if value == "vulkan" and label.endswith("[auto-detected]")))) self.assertTrue(labels[default_index].endswith("[auto-detected]")) self.assertEqual(options[default_index][1], "vulkan") def test_unknown_detected_backend_is_ignored(self): options, default_index = make_server.catalog._backend_options("opencl") self.assertEqual(default_index, 0) self.assertFalse(any("[auto-detected]" in label for label, _ in options)) def test_labels_keep_backend_values(self): options, _ = make_server.catalog._backend_options() self.assertEqual([value for _, value in options], list(make_server.BACKENDS)) def test_darwin_offers_only_cpu(self): with patch("sys.platform", "darwin"): options, default_index = make_server.catalog._backend_options() self.assertEqual(options, [("cpu - Apple Metal", "cpu")]) self.assertEqual(default_index, 0) def test_darwin_marks_detected_cpu(self): with patch("sys.platform", "darwin"): options, default_index = make_server.catalog._backend_options( "cpu") self.assertTrue(options[default_index][0].endswith("[auto-detected]")) self.assertEqual(options[default_index][1], "cpu") def test_windows_build_dir_names_map_to_backends(self): for name, backend in (("windows-cuda-release", "cuda"), ("windows-vulkan-release", "vulkan"), ("windows-cpu-release", "cpu"), ("windows-hip-release", "hip"), ("macos-metal-release", "cpu")): match = make_server.catalog._BACKEND_TOKEN_RE.search(name) self.assertIsNotNone(match, name) token = "cpu" if match.group(1) == "metal" else match.group(1) self.assertEqual(token, backend, name) class BuildServerConfigTests(unittest.TestCase): def test_single_entry_without_voice_dir(self): entry = make_server.catalog.build_model_entry( "higgs_audio_tts", "higgs", "models/Higgs-GGUF") cfg = make_server.catalog.build_server_config( "127.0.0.1", 8080, "cuda", False, [entry]) self.assertEqual(cfg["host"], "127.0.0.1") self.assertEqual(cfg["port"], 8080) self.assertEqual(cfg["backend"], "cuda") self.assertFalse(cfg["lazy_load"]) self.assertEqual(cfg["models"], [entry]) self.assertNotIn("voice_dir", cfg) def test_voice_dir_added_when_given(self): entry = make_server.catalog.build_model_entry("voxcpm2", "voxcpm2", "models/V") cfg = make_server.catalog.build_server_config( "0.0.0.0", 9000, "cpu", True, [entry], voice_dir="/abs/voices") self.assertTrue(cfg["lazy_load"]) self.assertEqual(cfg["voice_dir"], "/abs/voices") def test_model_entry_shape(self): entry = make_server.catalog.build_model_entry("index_tts2", "indextts2", "p") self.assertEqual(entry["id"], "indextts2") self.assertEqual(entry["family"], "index_tts2") self.assertEqual(entry["path"], "p") self.assertEqual(entry["task"], "tts") self.assertEqual(entry["mode"], "offline") def test_model_entry_design_task(self): entry = make_server.catalog.build_model_entry( "qwen3_tts", "qwen-design", "p", task="vdes") self.assertEqual(entry["task"], "vdes") self.assertEqual(entry["mode"], "offline") class CloneOnlyHostingTests(unittest.TestCase): """Clone-only family classification and server.json hosting tasks.""" def test_clone_only_set_members(self): for family in ("chatterbox", "confucius4_tts", "echo_tts"): self.assertTrue(make_server.is_clone_only_family(family)) def test_clone_only_from_spec_tasks(self): self.assertTrue(make_server.is_clone_only_family( "future_tts", tasks={"clone"})) def test_mixed_and_pure_families_are_not_clone_only(self): self.assertFalse(make_server.is_clone_only_family( "higgs_audio_tts", tasks={"tts", "clone"})) self.assertFalse(make_server.is_clone_only_family( "supertonic", tasks={"tts"})) def test_unknown_family_without_tasks_is_not_clone_only(self): # No spec, no explicit knowledge: keep the generic (tts) hosting. self.assertFalse(make_server.is_clone_only_family("brand_new")) def test_hosting_task_clone_only_family(self): self.assertEqual(make_server.hosting_task( {"family": "chatterbox", "tasks": ["tts", "clone", "vc"]}), "clon") def test_hosting_task_regular_family(self): self.assertEqual(make_server.hosting_task( {"family": "f5_tts", "tasks": ["tts", "clone"]}), "tts") class BuildEntriesHostingTests(unittest.TestCase): """_build_entries hosts clone-only families with task "clon".""" @staticmethod def _catalog_entry(family, tasks): return {"family": family, "display_name": family, "description": "", "languages": ["en"], "tasks": tasks, "clone_capable": "clone" in tasks, "packages": [], "install_id": f"{family}_q8_0", "default_path": f"models/{family}-GGUF"} @staticmethod def _option(directory): return {"target_directory": directory, "install_id": "pkg", "design": False, "recommended": True} def _entries(self, catalog_entry): entries, _, _, _, _, _ = make_server.wizard._build_entries( [catalog_entry["family"]], {catalog_entry["family"]: [self._option(catalog_entry["family"])]}, {catalog_entry["family"]: catalog_entry}, lambda install_id: "tts") return entries def test_chatterbox_is_hosted_with_clon(self): entry = self._entries(self._catalog_entry( "chatterbox", ["tts", "clone", "vc"]))[0] self.assertEqual(entry["task"], "clon") self.assertEqual(entry["family"], "chatterbox") def test_clone_only_spec_family_is_hosted_with_clon(self): entry = self._entries(self._catalog_entry( "confucius4_tts", ["clone"]))[0] self.assertEqual(entry["task"], "clon") def test_mixed_family_is_hosted_with_tts(self): entry = self._entries(self._catalog_entry( "f5_tts", ["tts", "clone"]))[0] self.assertEqual(entry["task"], "tts") class RehostCloneOnlyEntriesTests(unittest.TestCase): """server.json repair: clone-only entries re-hosted from "tts".""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.server_json = Path(self._td.name) / "server.json" def tearDown(self): self._td.cleanup() def _data(self, *models): return {"host": "127.0.0.1", "port": 8080, "backend": "cuda", "lazy_load": False, "models": list(models)} def _read(self): return json.loads(self.server_json.read_text(encoding="utf-8")) def test_chatterbox_tts_entry_is_rehosted_and_persisted(self): data = self._data({"id": "Chatterbox-GGUF", "family": "chatterbox", "path": "models/Chatterbox-GGUF", "task": "tts", "mode": "offline"}) repaired = make_server.rehost_clone_only_entries(self.server_json, data) self.assertEqual(repaired, ["Chatterbox-GGUF"]) self.assertEqual(data["models"][0]["task"], "clon") # The fix is written back so the server picks it up on restart. self.assertEqual(self._read()["models"][0]["task"], "clon") def test_non_clone_only_entries_are_untouched(self): data = self._data({"id": "q", "family": "qwen3_tts", "path": "models/Q", "task": "tts", "mode": "offline"}) self.assertEqual(make_server.rehost_clone_only_entries( self.server_json, data), []) self.assertEqual(data["models"][0]["task"], "tts") self.assertFalse(self.server_json.exists()) def test_vdes_and_clon_tasks_are_left_alone(self): data = self._data({"id": "c", "family": "chatterbox", "path": "m", "task": "clon", "mode": "offline"}, {"id": "d", "family": "qwen3_tts", "path": "m2", "task": "vdes", "mode": "offline"}) self.assertEqual(make_server.rehost_clone_only_entries( self.server_json, data), []) def test_unusable_document_is_ignored(self): self.assertEqual(make_server.rehost_clone_only_entries( self.server_json, {"models": "nope"}), []) self.assertEqual(make_server.rehost_clone_only_entries( self.server_json, {}), []) class InstallModelsTests(unittest.TestCase): """Printing or auto-running the model install commands.""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.checkout = Path(self._td.name) / "audio.cpp" self.checkout.mkdir() self.manager = self.checkout / "tools" / "model_manager_v2.py" self.manager.parent.mkdir() self.manager.write_text("#!/usr/bin/env python3\n", encoding="utf-8") self.guidance = [("Higgs Audio v3 TTS 4B", "higgs_audio_tts_4b_q8_0"), ("Qwen3-TTS", "qwen3_tts_1_7b_base_q8_0"), ("Qwen3-TTS", "qwen3_tts_1_7b_base_q8_0")] def tearDown(self): self._td.cleanup() def test_declined_download_prints_commands_deduped(self): buf = io.StringIO() with redirect_stdout(buf), \ patch.object(common, "run_console_subprocess") as run: make_server.models._install_models(self.checkout, self.guidance, download=False) out = buf.getvalue() self.assertEqual(out.count("install higgs_audio_tts_4b_q8_0"), 1) self.assertEqual(out.count("install qwen3_tts_1_7b_base_q8_0"), 1) run.assert_not_called() def test_accepted_download_runs_each_command(self): with patch.object(common, "run_console_subprocess", return_value=0) as run: make_server.models._install_models(self.checkout, self.guidance, download=True) self.assertEqual(run.call_count, 2) commands = [call[0][0] for call in run.call_args_list] self.assertEqual(commands[0], [sys.executable, str(self.manager), "install", "higgs_audio_tts_4b_q8_0"]) self.assertEqual(commands[1], [sys.executable, str(self.manager), "install", "qwen3_tts_1_7b_base_q8_0"]) for call in run.call_args_list: self.assertEqual(call[1]["cwd"], str(self.checkout)) def test_missing_manager_falls_back_to_printing(self): self.manager.unlink() buf = io.StringIO() with redirect_stdout(buf), \ patch.object(common, "run_console_subprocess") as run: make_server.models._install_models(self.checkout, self.guidance, download=True) self.assertIn("install higgs_audio_tts_4b_q8_0", buf.getvalue()) run.assert_not_called() def test_failed_install_reports_warning_and_continues(self): results = iter([1, 0]) buf = io.StringIO() with redirect_stdout(buf), \ patch.object(common, "run_console_subprocess", side_effect=lambda *a, **k: next(results)) as run: make_server.models._install_models(self.checkout, self.guidance, download=True) self.assertEqual(run.call_count, 2) self.assertIn("exited with code 1", buf.getvalue()) def _entry_paths(self): return [{"path": "models/higgs"}, {"path": "models/qwen"}] def test_installed_model_prints_no_command_for_it(self): # Mixed selection: qwen is on disk, higgs is not. The print path # reports the installed one without a python command, explains # that setup downloads automatically, then lists the rest. (self.checkout / "models" / "qwen").mkdir(parents=True) (self.checkout / "models" / "qwen" / "f.bin").write_bytes(b"x") buf = io.StringIO() with redirect_stdout(buf), \ patch.object(common, "run_console_subprocess") as run: make_server.models._install_models( self.checkout, [("Higgs Audio v3 TTS 4B", "higgs_audio_tts_4b_q8_0"), ("Qwen3-TTS", "qwen3_tts_1_7b_base_q8_0")], download=False, model_entries=self._entry_paths()) out = buf.getvalue() self.assertIn("[OK] Qwen3-TTS is already installed.", out) self.assertIn("downloaded automatically", out) self.assertIn("python {} install higgs_audio_tts_4b_q8_0".format( self.manager), out) self.assertNotIn("install qwen3_tts_1_7b_base_q8_0", out) run.assert_not_called() def test_all_models_present_prints_no_commands(self): for name in ("higgs", "qwen"): target = self.checkout / "models" / name target.mkdir(parents=True) (target / "f.bin").write_bytes(b"x") buf = io.StringIO() with redirect_stdout(buf), \ patch.object(common, "run_console_subprocess") as run: rc = make_server.models._install_models( self.checkout, [("Higgs Audio v3 TTS 4B", "higgs_audio_tts_4b_q8_0"), ("Qwen3-TTS", "qwen3_tts_1_7b_base_q8_0")], download=False, model_entries=self._entry_paths()) out = buf.getvalue() self.assertEqual(rc, 0) self.assertIn("All selected models are already installed.", out) self.assertNotIn("model_manager_v2.py install", out) run.assert_not_called() def test_download_skips_installed_models(self): (self.checkout / "models" / "qwen").mkdir(parents=True) (self.checkout / "models" / "qwen" / "f.bin").write_bytes(b"x") with redirect_stdout(io.StringIO()), \ patch.object(common, "run_console_subprocess", return_value=0) as run: make_server.models._install_models( self.checkout, [("Higgs Audio v3 TTS 4B", "higgs_audio_tts_4b_q8_0"), ("Qwen3-TTS", "qwen3_tts_1_7b_base_q8_0")], download=True, model_entries=self._entry_paths()) self.assertEqual(run.call_count, 1) self.assertEqual(run.call_args[0][0][3], "higgs_audio_tts_4b_q8_0") def test_entries_without_guidance_do_not_filter(self): # A length mismatch means no filtering is possible: every model # is treated as missing (the pre-change behavior). buf = io.StringIO() with redirect_stdout(buf): make_server.models._install_models( self.checkout, self.guidance, download=False, model_entries=[{"path": "models/qwen"}]) out = buf.getvalue() self.assertIn("higgs_audio_tts_4b_q8_0", out) self.assertIn("qwen3_tts_1_7b_base_q8_0", out) def test_download_applicable_false_without_manager(self): self.manager.unlink() self.assertFalse( make_server.models.download_applicable(self.checkout, [])) def test_download_applicable_when_manager_present(self): self.assertTrue( make_server.models.download_applicable(self.checkout, [])) def test_download_applicable_skipped_when_all_models_present(self): target = self.checkout / "models" / "higgs" target.mkdir(parents=True) (target / "model.gguf").write_bytes(b"x") self.assertFalse(make_server.models.download_applicable( self.checkout, [{"path": "models/higgs"}])) def test_download_applicable_when_a_model_is_missing(self): target = self.checkout / "models" / "higgs" target.mkdir(parents=True) (target / "model.gguf").write_bytes(b"x") self.assertTrue(make_server.models.download_applicable( self.checkout, [{"path": "models/higgs"}, {"path": "models/absent"}])) def test_all_models_present_true_when_all_paths_hold_files(self): target = self.checkout / "models" / "higgs" target.mkdir(parents=True) (target / "model.gguf").write_bytes(b"x") self.assertTrue(make_server.models._all_models_present( self.checkout, [{"path": "models/higgs"}])) def test_all_models_present_false_when_one_missing(self): target = self.checkout / "models" / "higgs" target.mkdir(parents=True) (target / "model.gguf").write_bytes(b"x") self.assertFalse(make_server.models._all_models_present( self.checkout, [{"path": "models/higgs"}, {"path": "models/absent"}])) def test_all_models_present_false_for_empty_selection(self): self.assertFalse(make_server.models._all_models_present(self.checkout, [])) def test_all_models_present_honors_absolute_paths(self): target = self.checkout / "models" / "higgs" target.mkdir(parents=True) (target / "model.gguf").write_bytes(b"x") self.assertTrue(make_server.models._all_models_present( self.checkout, [{"path": str(target)}])) def test_all_models_present_false_for_empty_dir(self): (self.checkout / "models" / "higgs").mkdir(parents=True) self.assertFalse(make_server.models._all_models_present( self.checkout, [{"path": "models/higgs"}])) def _supporting_manager(self): self.manager.write_text( '#!/usr/bin/env python3\n' 'parser.add_argument("--specs-dir", default="")\n' 'parser.add_argument("--cancel-file", default="")\n', encoding="utf-8") def _write_spec(self, name: str, packages: list) -> Path: specs = self.checkout / "model_specs" specs.mkdir(parents=True, exist_ok=True) path = specs / name path.write_text(json.dumps({"family": name[:-5], "packages": packages}), encoding="utf-8") return path def test_dot_strip_prefix_spec_installs_from_sanitized_copy(self): self._supporting_manager() original = self._write_spec("vietneu_tts.json", [{ "id": "vietneu_tts_v3_turbo_q8_0", "files": ["model.gguf"], "strip_prefix": ".", }]) self._write_spec("other.json", [{ "id": "other_q8_0", "files": ["Other-GGUF/model.gguf"], "strip_prefix": "Other-GGUF", }]) seen: dict = {} def record(argv, **kwargs): specs_dir = Path(argv[3]) seen["fixed"] = json.loads( (specs_dir / "vietneu_tts.json").read_text(encoding="utf-8")) seen["other"] = json.loads( (specs_dir / "other.json").read_text(encoding="utf-8")) return 0 buf = io.StringIO() with redirect_stdout(buf), \ patch.object(common, "run_console_subprocess", side_effect=record) as run: rc = make_server.models._install_models( self.checkout, [("VieNeu-TTS v3 Turbo GGUF", "vietneu_tts_v3_turbo_q8_0")], download=True) self.assertEqual(rc, 0) argv = run.call_args[0][0] self.assertEqual(argv[:2], [sys.executable, str(self.manager)]) self.assertEqual(argv[2], "--specs-dir") self.assertEqual(argv[4:], ["install", "vietneu_tts_v3_turbo_q8_0"]) self.assertEqual(seen["fixed"]["packages"][0]["strip_prefix"], "") self.assertEqual(seen["other"]["packages"][0]["strip_prefix"], "Other-GGUF") self.assertIn("sanitized copy", buf.getvalue()) # The checkout's own specs are untouched and the temp copy is gone. self.assertEqual(json.loads( original.read_text(encoding="utf-8"))["packages"][0] ["strip_prefix"], ".") self.assertFalse(Path(argv[3]).exists()) def test_healthy_specs_do_not_add_specs_dir(self): self._supporting_manager() self._write_spec("ok.json", [{ "id": "ok_q8_0", "files": ["Ok-GGUF/model.gguf"], "strip_prefix": "Ok-GGUF", }]) with patch.object(common, "run_console_subprocess", return_value=0) as run: make_server.models._install_models( self.checkout, [("Ok", "ok_q8_0")], download=True) self.assertEqual( run.call_args[0][0], [sys.executable, str(self.manager), "install", "ok_q8_0"]) def test_unknown_prefix_mismatch_left_for_warning_path(self): # A real-directory prefix that matches no files cannot be repaired # confidently; the install is left to fail with the manager's own # error so the remaining downloads continue (warn-and-continue). self._supporting_manager() self._write_spec("broken.json", [{ "id": "broken_q8_0", "files": ["model.gguf"], "strip_prefix": "Some-Dir", }]) with patch.object(common, "run_console_subprocess", return_value=0) as run: make_server.models._install_models( self.checkout, [("Broken", "broken_q8_0")], download=True) self.assertEqual( run.call_args[0][0], [sys.executable, str(self.manager), "install", "broken_q8_0"]) def test_specs_dir_unsupported_manager_leaves_argv_unchanged(self): self._write_spec("vietneu_tts.json", [{ "id": "vietneu_tts_v3_turbo_q8_0", "files": ["model.gguf"], "strip_prefix": ".", }]) with patch.object(common, "run_console_subprocess", return_value=0) as run: make_server.models._install_models( self.checkout, [("VieNeu-TTS v3 Turbo GGUF", "vietneu_tts_v3_turbo_q8_0")], download=True) self.assertEqual( run.call_args[0][0], [sys.executable, str(self.manager), "install", "vietneu_tts_v3_turbo_q8_0"]) class SanitizeModelSpecTests(unittest.TestCase): """The dot strip_prefix repair and the --specs-dir staging copy.""" def test_dot_prefix_dropped_when_file_is_bare(self): spec = {"packages": [{"files": ["model.gguf"], "strip_prefix": "."}]} self.assertTrue(make_server.models._sanitize_model_spec(spec)) self.assertEqual(spec["packages"][0]["strip_prefix"], "") def test_slash_dot_prefix_normalized_like_dot(self): spec = {"packages": [{"files": ["model.gguf"], "strip_prefix": "./"}]} self.assertTrue(make_server.models._sanitize_model_spec(spec)) self.assertEqual(spec["packages"][0]["strip_prefix"], "") def test_dot_prefix_kept_when_files_carry_it(self): spec = {"packages": [{"files": ["./model.gguf"], "strip_prefix": "."}]} self.assertFalse(make_server.models._sanitize_model_spec(spec)) self.assertEqual(spec["packages"][0]["strip_prefix"], ".") def test_real_directory_prefix_untouched(self): spec = {"packages": [{"files": ["model.gguf"], "strip_prefix": "Kroko-ASR-GGUF"}]} self.assertFalse(make_server.models._sanitize_model_spec(spec)) self.assertEqual(spec["packages"][0]["strip_prefix"], "Kroko-ASR-GGUF") def test_valid_prefix_untouched(self): spec = {"packages": [{"files": ["Kroko-ASR-GGUF/model.gguf"], "strip_prefix": "Kroko-ASR-GGUF"}]} self.assertFalse(make_server.models._sanitize_model_spec(spec)) def test_missing_or_empty_files_untouched(self): spec = {"packages": [{"strip_prefix": "."}, {"files": [], "strip_prefix": "."}, {"files": "model.gguf", "strip_prefix": "."}]} self.assertFalse(make_server.models._sanitize_model_spec(spec)) def test_only_broken_packages_repaired(self): spec = {"packages": [ {"files": ["model.gguf"], "strip_prefix": "."}, {"files": ["./model.gguf"], "strip_prefix": "."}, ]} self.assertTrue(make_server.models._sanitize_model_spec(spec)) self.assertEqual([p["strip_prefix"] for p in spec["packages"]], ["", "."]) def setUp(self): self._td = tempfile.TemporaryDirectory() self.checkout = Path(self._td.name) / "audio.cpp" self.checkout.mkdir() def tearDown(self): self._td.cleanup() def test_prepare_specs_dir_none_without_specs(self): self.assertIsNone( make_server.models._prepare_specs_dir(self.checkout)) def test_prepare_specs_dir_none_when_healthy(self): specs = self.checkout / "model_specs" specs.mkdir() (specs / "ok.json").write_text(json.dumps( {"packages": [{"files": ["Ok-GGUF/m.gguf"], "strip_prefix": "Ok-GGUF"}]}), encoding="utf-8") self.assertIsNone( make_server.models._prepare_specs_dir(self.checkout)) def test_prepare_specs_dir_writes_all_specs_and_repairs(self): specs = self.checkout / "model_specs" specs.mkdir() (specs / "broken.json").write_text(json.dumps( {"packages": [{"files": ["model.gguf"], "strip_prefix": "."}]}), encoding="utf-8") (specs / "plain.json").write_text("not json", encoding="utf-8") staging = make_server.models._prepare_specs_dir(self.checkout) try: self.assertIsNotNone(staging) repaired = json.loads( (staging / "broken.json").read_text(encoding="utf-8")) self.assertEqual(repaired["packages"][0]["strip_prefix"], "") self.assertEqual( (staging / "plain.json").read_text(encoding="utf-8"), "not json") finally: shutil.rmtree(staging, ignore_errors=True) class ConfigFormTranscriptionToggleTests(unittest.TestCase): """The combined form's Voice transcripts row: one fixed two-way toggle. The row is always visible whenever a clone-capable family is hosted (it must not hide itself just because the picked wav directory has no .wavs yet), and the plan it produces follows the toggled mode. """ def _checkout(self): tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) return _make_checkout(Path(tmp.name)) def _empty_dir(self): tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) return Path(tmp.name) def _run(self, checkout, voices_dir, picked_family=None, override=None): """Drive _wizard on CHECKOUT; return (form call capture, settings).""" catalog = make_server.catalog.load_model_catalog(checkout) family = picked_family or "qwen3_tts" index = next(i for i, entry in enumerate(catalog) if entry["family"] == family) target = catalog[index]["packages"][0]["target_directory"] captured = {} def fake_form(stdscr, title, fields, **kwargs): captured.update(kwargs) captured["fields"] = fields result = {f["key"]: f["value"] for f in fields} if override: result.update(override) return result with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(tui, "checkbox_tree", return_value=[(index, target)]), \ patch.object(tui, "form", side_effect=fake_form), \ patch.object(make_server.wizard, "VOICES_DIR", voices_dir): settings = make_server.wizard._wizard( None, make_server.wizard.build_parser().parse_args([]), make_server.wizard.build_parser()) return captured, settings def test_row_is_a_fixed_toggle_defaulting_to_new_voices(self): checkout = self._checkout() captured, _settings = self._run(checkout, self._empty_dir()) by_key = {f["key"]: f for f in captured["fields"]} row = by_key["transcription"] self.assertEqual(row["kind"], "toggle") self.assertEqual(row["value"], "missing") self.assertEqual( row["choices"], [("Transcribe new voices", "missing"), ("Re-transcribe all voices", "all")]) def test_row_always_visible_when_clone_capable(self): # Regression: the row used to hide itself until the wav directory # contained .wavs; a clone-capable pick must always offer it. checkout = self._checkout() voices = self._empty_dir() captured, _settings = self._run(checkout, voices) by_key = {f["key"]: f for f in captured["fields"]} self.assertTrue(by_key["transcription"]["visible"](captured["fields"])) self.assertEqual(_settings["plan"]["mode"], "missing") self.assertEqual(_settings["plan"]["missing"], []) def test_row_hidden_without_a_clone_capable_pick(self): checkout = self._checkout() captured, _settings = self._run(checkout, self._empty_dir(), picked_family="supertonic") by_key = {f["key"]: f for f in captured["fields"]} self.assertFalse(by_key["transcription"]["visible"](captured["fields"])) self.assertIsNone(_settings["plan"]) def test_form_opens_on_the_continue_button(self): checkout = self._checkout() captured, _settings = self._run(checkout, self._empty_dir()) self.assertTrue(captured.get("start_on_buttons")) def test_new_voices_plan_carries_only_untranscribed_wavs(self): checkout = self._checkout() voices = self._empty_dir() (voices / "extra.wav").write_bytes(b"x") (voices / "narrator.wav").write_bytes(b"x") common.write_prompt_text(voices, {"narrator": "Old words."}) _captured, settings = self._run(checkout, voices) self.assertEqual(settings["plan"]["mode"], "missing") self.assertEqual([w.name for w in settings["plan"]["missing"]], ["extra.wav"]) self.assertEqual(settings["plan"]["existing"], {"narrator": "Old words."}) def test_toggled_all_retranscribes_everything(self): checkout = self._checkout() voices = self._empty_dir() (voices / "narrator.wav").write_bytes(b"x") common.write_prompt_text(voices, {"narrator": "Old words."}) _captured, settings = self._run(checkout, voices, override={"transcription": "all"}) self.assertEqual(settings["plan"]["mode"], "all") class TranscribeWavDirTests(unittest.TestCase): def setUp(self): self._td = tempfile.TemporaryDirectory() self.folder = Path(self._td.name) self.narrator = self.folder / "narrator.wav" self.narrator.write_bytes(b"x") self.other = self.folder / "other.wav" self.other.write_bytes(b"x") def tearDown(self): self._td.cleanup() def test_transcribes_to_stem_map_with_absolute_paths(self): transcripts = {str(self.narrator): ("First.", "ok"), str(self.other): ("Second.", "ok")} with patch.object(make_server.voices, "transcribe_reference_audio_detailed", side_effect=lambda path, model_name="base": transcripts[path]): result = make_server.voices.transcribe_wav_dir( [self.narrator, self.other], "base") self.assertEqual(list(result), ["narrator", "other"]) self.assertEqual(result["narrator"], "First.") def test_failed_transcription_keeps_empty_string(self): with patch.object(make_server.voices, "transcribe_reference_audio_detailed", return_value=(None, "no speech detected")): result = make_server.voices.transcribe_wav_dir([self.narrator], "base") self.assertEqual(result["narrator"], "") def test_failed_transcription_prints_the_reason(self): buffer = io.StringIO() with patch.object(make_server.voices, "transcribe_reference_audio_detailed", return_value=(None, "faster_whisper heard no speech")), \ redirect_stdout(buffer): make_server.voices.transcribe_wav_dir([self.narrator], "base") output = buffer.getvalue() self.assertIn("No transcript for 'narrator'", output) self.assertIn("faster_whisper heard no speech", output) def test_whisper_model_name_passed_through(self): with patch.object(make_server.voices, "transcribe_reference_audio_detailed", return_value=("text", "ok")) as mock_transcribe: make_server.voices.transcribe_wav_dir([self.narrator], "large-v3") self.assertEqual(mock_transcribe.call_args.kwargs["model_name"], "large-v3") def test_write_prompt_text_format(self): path = common.write_prompt_text( self.folder, {"narrator": "Hello.", "other": "World."}) self.assertEqual(path, self.folder / common.PROMPT_TEXT_FILENAME) text = path.read_text(encoding="utf-8") self.assertIn("narrator|Hello.", text) self.assertIn("other|World.", text) class TranscribePlanTests(unittest.TestCase): """_transcribe: plan application and transcript wipe protection.""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.folder = Path(self._td.name) self.narrator = self.folder / "narrator.wav" self.narrator.write_bytes(b"x") self.args = argparse.Namespace(input_dir=self.folder, whisper_model="base") def tearDown(self): self._td.cleanup() def test_all_mode_retranscribes_everything(self): with patch.object(make_server.voices, "transcribe_reference_audio_detailed", return_value=("New words.", "ok")): transcripts, write = make_server.voices._transcribe( self.args, {"mode": "all", "missing": [], "existing": {}}) self.assertTrue(write) self.assertEqual(transcripts, {"narrator": "New words."}) def test_empty_retranscription_keeps_existing_transcript(self): # A failed re-transcription must never overwrite known-good text # with a blank: a blank prompt_text entry makes the server reject # every clone request for that voice. with patch.object(make_server.voices, "transcribe_reference_audio_detailed", return_value=(None, "backend broken")), \ redirect_stdout(io.StringIO()) as buffer: transcripts, write = make_server.voices._transcribe( self.args, {"mode": "all", "missing": [], "existing": {"narrator": "Good words."}}) self.assertTrue(write) self.assertEqual(transcripts, {"narrator": "Good words."}) self.assertIn("Kept the existing transcript for 'narrator'", buffer.getvalue()) def test_missing_mode_merges_new_with_existing(self): with patch.object(make_server.voices, "transcribe_reference_audio_detailed", return_value=("Fresh text.", "ok")): transcripts, _write = make_server.voices._transcribe( self.args, {"mode": "missing", "missing": [self.narrator], "existing": {}}) self.assertEqual(transcripts, {"narrator": "Fresh text."}) def test_unusable_backend_warns_with_the_reason(self): buffer = io.StringIO() with patch.object(make_server.voices, "whisper_backend_problem", return_value="faster_whisper is installed but " "failed to import: boom"), \ patch.object(make_server.voices, "transcribe_reference_audio_detailed", return_value=("text", "ok")), \ redirect_stdout(buffer): make_server.voices._transcribe( self.args, {"mode": "all", "missing": [], "existing": {}}) output = buffer.getvalue() self.assertIn("No usable Whisper backend", output) self.assertIn("failed to import: boom", output) class WizardTranscribeStepTests(unittest.TestCase): """The setup lane's transcribe step: rc reflects unusable transcripts.""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.folder = Path(self._td.name) (self.folder / "narrator.wav").write_bytes(b"x") self.args = argparse.Namespace(input_dir=None, whisper_model="base") self.settings = { "audiocpp_dir": self.folder, "wav_dir": self.folder, "include_clone": True, "plan": {"mode": "all", "missing": [], "existing": {}}, "build": None, "model_entries": [], } def tearDown(self): self._td.cleanup() def _transcribe_step(self): lanes = make_server.wizard._execute_lanes(self.settings, self.args) return lanes[0].steps[0] def test_blank_transcripts_fail_the_step(self): step = self._transcribe_step() with patch.object(make_server.voices, "transcribe_reference_audio_detailed", return_value=(None, "broken backend")), \ redirect_stdout(io.StringIO()) as buffer: rc = step.work(None, None) self.assertEqual(rc, 1) self.assertIn("No transcript for: narrator", buffer.getvalue()) def test_good_transcripts_pass_the_step(self): step = self._transcribe_step() with patch.object(make_server.voices, "transcribe_reference_audio_detailed", return_value=("Words.", "ok")), \ redirect_stdout(io.StringIO()): self.assertEqual(step.work(None, None), 0) def test_no_clone_families_passes_without_transcribing(self): self.settings["include_clone"] = False self.settings["plan"] = None step = self._transcribe_step() with patch.object(make_server.voices, "transcribe_reference_audio_detailed") as mock_transcribe: self.assertEqual(step.work(None, None), 0) mock_transcribe.assert_not_called() class DesignPackageTests(unittest.TestCase): """Voice-design package detection.""" def test_detects_voicedesign_in_id(self): self.assertTrue(make_server.catalog.is_design_package( {"id": "qwen3_tts_1_7b_voicedesign_q8_0"})) def test_detects_voicedesign_in_directory(self): self.assertTrue(make_server.catalog.is_design_package( {"target_directory": "Foo-VoiceDesign-GGUF"})) def test_detects_separated_voice_design(self): self.assertTrue(make_server.catalog.is_design_package( {"display_name": "Voice Design Q8_0"})) def test_ignores_other_packages(self): self.assertFalse(make_server.catalog.is_design_package( {"id": "higgs_audio_tts_4b_q8_0"})) self.assertFalse(make_server.catalog.is_design_package({})) class PackageDirOptionsTests(unittest.TestCase): """Grouping a family's packages into distinct target directories.""" def test_groups_precisions_and_marks_recommended(self): entry = { "family": "qwen3_tts", "packages": [ {"id": "base_q8", "default": True, "format": "gguf", "target_directory": "Base-GGUF"}, {"id": "base_bf16", "format": "gguf", "target_directory": "Base-GGUF"}, {"id": "voicedesign_q8", "format": "gguf", "target_directory": "VoiceDesign-GGUF"}, ], } options = make_server.catalog.package_dir_options(entry) self.assertEqual([o["target_directory"] for o in options], ["Base-GGUF", "VoiceDesign-GGUF"]) self.assertTrue(options[0]["recommended"]) self.assertFalse(options[0]["design"]) self.assertFalse(options[1]["recommended"]) self.assertTrue(options[1]["design"]) self.assertEqual(options[0]["install_id"], "base_q8") def test_recommended_comes_first_even_if_listed_later(self): entry = { "family": "demo_tts", "packages": [ {"id": "demo_other", "format": "gguf", "target_directory": "Other-GGUF"}, {"id": "demo_default", "default": True, "format": "gguf", "target_directory": "Default-GGUF"}, ], } options = make_server.catalog.package_dir_options(entry) self.assertEqual([o["target_directory"] for o in options], ["Default-GGUF", "Other-GGUF"]) class FindAudiocppServerBinTests(unittest.TestCase): """Locating the built audiocpp_server binary.""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.checkout = Path(self._td.name) / "audio.cpp" self.checkout.mkdir() def tearDown(self): self._td.cleanup() def _build(self, name, binary="audiocpp_server"): bin_dir = self.checkout / "build" / name / "bin" bin_dir.mkdir(parents=True) (bin_dir / binary).write_bytes(b"x") def test_no_build_dir_returns_none(self): self.assertIsNone(make_server.build.find_audiocpp_server_bin(self.checkout)) def test_finds_built_binary(self): self._build("linux-cuda-release") self.assertEqual( make_server.build.find_audiocpp_server_bin(self.checkout), self.checkout / "build" / "linux-cuda-release" / "bin" / "audiocpp_server") def test_finds_windows_exe(self): self._build("windows-vulkan-debug", binary="audiocpp_server.exe") self.assertEqual( make_server.build.find_audiocpp_server_bin(self.checkout).name, "audiocpp_server.exe") def test_build_dir_without_binary_returns_none(self): (self.checkout / "build" / "linux-cuda-release" / "bin").mkdir( parents=True) self.assertIsNone(make_server.build.find_audiocpp_server_bin(self.checkout)) class BuiltServerBinaryTests(unittest.TestCase): """built_server_binary: locating a specific backend's build.""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.checkout = Path(self._td.name) / "audio.cpp" self.checkout.mkdir() def tearDown(self): self._td.cleanup() def _build(self, name, binary="audiocpp_server"): bin_dir = self.checkout / "build" / name / "bin" bin_dir.mkdir(parents=True) (bin_dir / binary).write_bytes(b"x") def test_returns_the_matching_backend_binary(self): self._build("linux-cuda-release") self._build("linux-cpu-release") self.assertEqual( make_server.build.built_server_binary(self.checkout, "cpu"), self.checkout / "build" / "linux-cpu-release" / "bin" / "audiocpp_server") def test_returns_none_for_unbuilt_backend(self): self._build("linux-cuda-release") self.assertIsNone( make_server.build.built_server_binary(self.checkout, "vulkan")) def test_metal_counts_as_cpu(self): self._build("macos-metal-release") self.assertEqual( make_server.build.built_server_binary(self.checkout, "cpu"), self.checkout / "build" / "macos-metal-release" / "bin" / "audiocpp_server") def test_no_build_dir_returns_none(self): self.assertIsNone(make_server.build.built_server_binary(self.checkout, "cpu")) class BuildAudiocppTests(unittest.TestCase): """Running the audio.cpp build helper script.""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.checkout = Path(self._td.name) / "audio.cpp" self.checkout.mkdir() self.scripts = self.checkout / "scripts" self.scripts.mkdir() (self.scripts / "build_linux.sh").write_text("#!/bin/sh\n", encoding="utf-8") (self.scripts / "build_metal.sh").write_text("#!/bin/bash\n", encoding="utf-8") (self.scripts / "build_windows.ps1").write_text("param()\n", encoding="utf-8") (self.scripts / "build_windows_hip.ps1").write_text("param()\n", encoding="utf-8") self.log_dir = Path(self._td.name) / "logs" self.addCleanup(common.drain_post_tui_notices) def tearDown(self): self._td.cleanup() def _emit(self): lines = [] def emit(line): lines.append(line) return lines, emit def _log_files(self): return sorted(self.log_dir.glob("audiocpp_build_*.log")) def test_runs_build_script_with_backend_and_target(self): with patch.object(common, "run_console_subprocess", return_value=0) as run: rc = make_server.build.build_audiocpp(self.checkout, "cuda") self.assertEqual(rc, 0) argv = run.call_args[0][0] self.assertEqual(argv[:3], ["bash", str(self.scripts / "build_linux.sh"), "--backend"]) self.assertIn("cuda", argv) self.assertIn("--target", argv) self.assertIn("audiocpp_server", argv) self.assertIn("--deployment-build", argv) self.assertEqual(run.call_args[1]["cwd"], self.checkout) def test_darwin_uses_metal_script_without_backend(self): with patch.object(common, "run_console_subprocess", return_value=0) as run, \ patch.object(make_server.build, "_metal_compiler_available", return_value=True), \ patch("sys.platform", "darwin"): rc = make_server.build.build_audiocpp(self.checkout, "cpu") self.assertEqual(rc, 0) argv = run.call_args[0][0] self.assertEqual(argv[:2], ["bash", str(self.scripts / "build_metal.sh")]) self.assertNotIn("--backend", argv) self.assertIn("--target", argv) self.assertIn("audiocpp_server", argv) self.assertIn("--deployment-build", argv) self.assertEqual(run.call_args[1]["cwd"], self.checkout) def test_darwin_without_metal_compiler_runs_cmake_directly(self): # No Xcode (no offline Metal compiler): build_metal.sh would abort # at its probe, so the build must fall back to a direct cmake # invocation with the Metal shaders embedded as source — which # needs only the Command Line Tools. with patch.object(common, "run_console_subprocess", return_value=0) as run, \ patch.object(make_server.build, "_metal_compiler_available", return_value=False), \ patch("sys.platform", "darwin"): rc = make_server.build.build_audiocpp(self.checkout, "cpu") self.assertEqual(rc, 0) argv = run.call_args[0][0] self.assertEqual(argv[:2], ["bash", "-c"]) script = argv[2] self.assertIn("cmake -S . -B build/macos-metal-release", script) self.assertIn("-DENGINE_ENABLE_METAL=ON", script) self.assertIn("-DGGML_METAL_EMBED_LIBRARY=ON", script) self.assertIn("-DAUDIOCPP_DEPLOYMENT_BUILD=ON", script) self.assertIn("cmake --build build/macos-metal-release", script) self.assertIn("--target audiocpp_server", script) self.assertNotIn("build_metal.sh", script) self.assertEqual(run.call_args[1]["cwd"], self.checkout) def test_metal_compiler_probe(self): # The probe mirrors build_metal.sh's own check: a failing (or # missing) xcrun metal means the offline compiler is absent. with patch.object(common, "run_console_subprocess_quiet", return_value=None): self.assertFalse( make_server.build._metal_compiler_available()) with patch.object(common, "run_console_subprocess_quiet", return_value=MagicMock(returncode=0)): self.assertTrue(make_server.build._metal_compiler_available()) def test_darwin_cuda_backend_gets_no_arch_flags(self): with patch.object(common, "run_console_subprocess", return_value=0) as run, \ patch.object(make_server.build, "_metal_compiler_available", return_value=True), \ patch("sys.platform", "darwin"): rc = make_server.build.build_audiocpp(self.checkout, "cuda") self.assertEqual(rc, 0) argv = run.call_args[0][0] self.assertNotIn("--backend", argv) self.assertNotIn("--cuda-arch", argv) def test_windows_uses_powershell_with_preset(self): with patch.object(common, "run_console_subprocess", return_value=0) as run, \ patch("sys.platform", "win32"): rc = make_server.build.build_audiocpp(self.checkout, "vulkan") self.assertEqual(rc, 0) argv = run.call_args[0][0] self.assertEqual( argv[:6], ["powershell", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File"]) self.assertEqual(argv[6], str(self.scripts / "build_windows.ps1")) self.assertIn("-Preset", argv) self.assertIn("windows-vulkan-release", argv) self.assertIn("-Target", argv) self.assertIn("audiocpp_server", argv) self.assertIn("-DeploymentBuild", argv) self.assertNotIn("-CudaArchitectures", argv) self.assertEqual(run.call_args[1]["cwd"], self.checkout) def test_windows_cuda_arch_uses_powershell_flag(self): with patch.object(common, "run_console_subprocess", return_value=0) as run, \ patch("sys.platform", "win32"), \ patch.object(make_server.build, "detect_cuda_arch", return_value="86;89"): rc = make_server.build.build_audiocpp(self.checkout, "cuda") self.assertEqual(rc, 0) argv = run.call_args[0][0] self.assertIn("windows-cuda-release", argv) self.assertEqual(argv[-2:], ["-CudaArchitectures", "86;89"]) def test_windows_hip_uses_hip_script_and_token_dir(self): with patch.object(common, "run_console_subprocess", return_value=0) as run, \ patch("sys.platform", "win32"): rc = make_server.build.build_audiocpp(self.checkout, "hip") self.assertEqual(rc, 0) argv = run.call_args[0][0] self.assertEqual(argv[6], str(self.scripts / "build_windows_hip.ps1")) self.assertNotIn("-Preset", argv) self.assertIn("-BuildDir", argv) self.assertIn("build/windows-hip-release", argv) self.assertIn("-DeploymentBuild", argv) def test_missing_script_returns_nonzero(self): for f in self.scripts.iterdir(): f.unlink() rc = make_server.build.build_audiocpp(self.checkout, "cuda") self.assertNotEqual(rc, 0) def test_console_path_writes_no_log_and_no_notice(self): with patch.object(common, "LOG_DIR", self.log_dir), \ patch.object(common, "run_console_subprocess", return_value=0): rc = make_server.build.build_audiocpp(self.checkout, "cuda") self.assertEqual(rc, 0) self.assertEqual(self._log_files(), []) self.assertEqual(common.drain_post_tui_notices(), []) def test_tui_success_writes_log_and_no_notice(self): emitted, emit = self._emit() with patch.object(common, "LOG_DIR", self.log_dir), \ patch.object(common, "run_console_subprocess", return_value=0): rc = make_server.build.build_audiocpp(self.checkout, "cuda", emit=emit) self.assertEqual(rc, 0) self.assertEqual(len(self._log_files()), 1) log_text = self._log_files()[0].read_text(encoding="utf-8") self.assertIn("[INFO] Building audiocpp_server", log_text) self.assertIn("--backend cuda", log_text) self.assertTrue(emitted) self.assertEqual(common.drain_post_tui_notices(), []) def test_tui_failure_writes_log_and_records_notice(self): emitted, emit = self._emit() with patch.object(common, "LOG_DIR", self.log_dir), \ patch.object(common, "run_console_subprocess", return_value=3): rc = make_server.build.build_audiocpp(self.checkout, "cuda", emit=emit) self.assertEqual(rc, 3) logs = self._log_files() self.assertEqual(len(logs), 1) log_text = logs[0].read_text(encoding="utf-8") self.assertIn("failed (exit code 3)", log_text) notices = common.drain_post_tui_notices() self.assertEqual(len(notices), 1) notice = notices[0] self.assertIn("failed (exit code 3)", notice) self.assertIn(f"Build log: {logs[0]}", notice) command = (f"cd {self.checkout} && bash " f"{self.scripts / 'build_linux.sh'} --backend cuda " "--target audiocpp_server --deployment-build") self.assertIn(command, notice) self.assertIn("Troubleshoot by re-running this command", notice) self.assertTrue(any("failed (exit code 3)" in line for line in emitted)) def test_tui_cancel_suppresses_notice_but_writes_log(self): emitted, emit = self._emit() cancel = threading.Event() cancel.set() with patch.object(common, "LOG_DIR", self.log_dir), \ patch.object(common, "run_console_subprocess", return_value=130): rc = make_server.build.build_audiocpp(self.checkout, "cuda", emit=emit, cancel=cancel) self.assertEqual(rc, 130) self.assertEqual(len(self._log_files()), 1) self.assertEqual(common.drain_post_tui_notices(), []) def test_tui_missing_script_records_guidance_notice(self): for f in self.scripts.iterdir(): f.unlink() emitted, emit = self._emit() with patch.object(common, "LOG_DIR", self.log_dir): rc = make_server.build.build_audiocpp(self.checkout, "cuda", emit=emit) self.assertNotEqual(rc, 0) self.assertEqual(self._log_files(), []) notices = common.drain_post_tui_notices() self.assertEqual(len(notices), 1) self.assertIn("No build script found", notices[0]) def test_cuda_build_appends_detected_arch_flag(self): with patch.object(common, "run_console_subprocess", return_value=0) as run, \ patch.object(make_server.build, "detect_cuda_arch", return_value="86"): make_server.build.build_audiocpp(self.checkout, "cuda") argv = run.call_args[0][0] self.assertIn("--cuda-arch", argv) self.assertEqual(argv[argv.index("--cuda-arch") + 1], "86") def test_tui_stall_notice_reports_the_hang(self): emitted, emit = self._emit() with patch.object(common, "LOG_DIR", self.log_dir), \ patch.object(common, "run_console_subprocess", return_value=124): rc = make_server.build.build_audiocpp(self.checkout, "cuda", emit=emit) self.assertEqual(rc, 124) log_text = self._log_files()[0].read_text(encoding="utf-8") self.assertIn("stalled", log_text) notices = common.drain_post_tui_notices() self.assertEqual(len(notices), 1) self.assertIn("stalled", notices[0]) self.assertIn("stopped", notices[0]) def test_tui_ptxas_failure_appends_detected_arch_guidance(self): emitted, emit = self._emit() def fake_run(argv, cwd=None, emit=None, cancel=None, **kwargs): emit("ptxas fatal : (C7907) Internal compiler error.") return 1 with patch.object(common, "LOG_DIR", self.log_dir), \ patch.object(common, "run_console_subprocess", side_effect=fake_run), \ patch.object(make_server.build, "detect_cuda_arch", return_value="86"): rc = make_server.build.build_audiocpp(self.checkout, "cuda", emit=emit) self.assertEqual(rc, 1) notice = common.drain_post_tui_notices()[0] self.assertIn("CUDA toolkit bug", notice) self.assertIn("AUDIOCPP_CUDA_ARCH", notice) self.assertIn("Detected arch for this machine: 86", notice) # The undetected-GPU table is not needed when detection worked. self.assertNotIn("Arch per GPU", notice) def test_tui_ptxas_failure_lists_gpu_table_when_undetected(self): emitted, emit = self._emit() def fake_run(argv, cwd=None, emit=None, cancel=None, **kwargs): emit("ptxas fatal : (C7907) Internal compiler error.") return 1 with patch.object(common, "LOG_DIR", self.log_dir), \ patch.object(common, "run_console_subprocess", side_effect=fake_run), \ patch.object(make_server.build, "detect_cuda_arch", return_value=None): rc = make_server.build.build_audiocpp(self.checkout, "cuda", emit=emit) self.assertEqual(rc, 1) notice = common.drain_post_tui_notices()[0] self.assertIn("Arch per GPU", notice) self.assertIn("3090", notice) self.assertIn("4090", notice) def test_tui_plain_failure_has_no_ptxas_guidance(self): emitted, emit = self._emit() with patch.object(common, "LOG_DIR", self.log_dir), \ patch.object(common, "run_console_subprocess", return_value=3): rc = make_server.build.build_audiocpp(self.checkout, "cuda", emit=emit) notice = common.drain_post_tui_notices()[0] self.assertIn("failed (exit code 3)", notice) self.assertNotIn("AUDIOCPP_CUDA_ARCH", notice) class FindBuildScriptTests(unittest.TestCase): """Per-platform helper script selection (find_build_script).""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.addCleanup(self._td.cleanup) self.checkout = Path(self._td.name) / "audio.cpp" self.scripts = self.checkout / "scripts" self.scripts.mkdir(parents=True) def _write(self, name): (self.scripts / name).write_text("#!/bin/sh\n", encoding="utf-8") def test_linux_prefers_build_linux(self): for name in ("build_linux.sh", "build_metal.sh"): self._write(name) with patch("sys.platform", "linux"): self.assertEqual( make_server.build.find_build_script(self.checkout), self.scripts / "build_linux.sh") def test_darwin_prefers_build_metal(self): for name in ("build_linux.sh", "build_metal.sh"): self._write(name) with patch("sys.platform", "darwin"): self.assertEqual( make_server.build.find_build_script(self.checkout), self.scripts / "build_metal.sh") def test_darwin_falls_back_to_first_sh(self): self._write("build_linux.sh") with patch("sys.platform", "darwin"): self.assertEqual( make_server.build.find_build_script(self.checkout), self.scripts / "build_linux.sh") def test_windows_selects_powershell_scripts(self): self._write("build_windows.ps1") self._write("build_windows_hip.ps1") self._write("build_linux.sh") with patch("sys.platform", "win32"): find = make_server.build.find_build_script self.assertEqual(find(self.checkout), self.scripts / "build_windows.ps1") self.assertEqual(find(self.checkout, "cuda"), self.scripts / "build_windows.ps1") self.assertEqual(find(self.checkout, "cpu"), self.scripts / "build_windows.ps1") self.assertEqual(find(self.checkout, "hip"), self.scripts / "build_windows_hip.ps1") def test_windows_without_scripts_returns_none(self): self._write("build_linux.sh") with patch("sys.platform", "win32"): self.assertIsNone(make_server.build.find_build_script(self.checkout)) def test_missing_scripts_dir_returns_none(self): self.assertIsNone(make_server.build.find_build_script(self.checkout)) class DetectCudaArchTests(unittest.TestCase): """detect_cuda_arch: env override, nvidia-smi probe, None fallbacks.""" def setUp(self): # Run every case without an AUDIOCPP_CUDA_ARCH leak from the # developer's own shell. clean = {k: v for k, v in os.environ.items() if k != make_server.build.CUDA_ARCH_ENV} patcher = patch.dict(os.environ, clean, clear=True) patcher.start() self.addCleanup(patcher.stop) def test_env_override_wins_without_probing(self): with patch.dict(os.environ, {make_server.build.CUDA_ARCH_ENV: "86"}), \ patch.object(common, "run_console_subprocess_quiet") as run: self.assertEqual(make_server.build.detect_cuda_arch(), "86") run.assert_not_called() def test_env_override_multi_gpu_and_commas(self): with patch.dict(os.environ, {make_server.build.CUDA_ARCH_ENV: "86, 89;75"}): self.assertEqual(make_server.build.detect_cuda_arch(), "86;89;75") def test_env_override_real_virtual_suffixes_allowed(self): with patch.dict(os.environ, {make_server.build.CUDA_ARCH_ENV: "86-real"}): self.assertEqual(make_server.build.detect_cuda_arch(), "86-real") def test_invalid_env_override_ignored_and_probe_runs(self): probe = MagicMock(returncode=0, stdout=b"8.6\n") with patch.dict(os.environ, {make_server.build.CUDA_ARCH_ENV: "rtx"}), \ patch.object(common, "run_console_subprocess_quiet", return_value=probe) as run: self.assertEqual(make_server.build.detect_cuda_arch(), "86") self.assertEqual(run.call_args[0][0][0], "nvidia-smi") def test_compute_caps_parsed_and_deduped(self): probe = MagicMock(returncode=0, stdout=b"8.6\n8.6\n12.0\n") with patch.object(common, "run_console_subprocess_quiet", return_value=probe): self.assertEqual(make_server.build.detect_cuda_arch(), "86;120") def test_nvidia_smi_failure_yields_none(self): probe = MagicMock(returncode=1, stdout=b"") with patch.object(common, "run_console_subprocess_quiet", return_value=probe): self.assertIsNone(make_server.build.detect_cuda_arch()) def test_unparsable_output_yields_none(self): probe = MagicMock(returncode=0, stdout=b"NVIDIA-SMI has failed because...\n") with patch.object(common, "run_console_subprocess_quiet", return_value=probe): self.assertIsNone(make_server.build.detect_cuda_arch()) def test_unstartable_probe_yields_none(self): with patch.object(common, "run_console_subprocess_quiet", return_value=None): self.assertIsNone(make_server.build.detect_cuda_arch()) def test_probe_is_bounded_by_a_timeout(self): probe = MagicMock(returncode=0, stdout=b"8.6\n") with patch.object(common, "run_console_subprocess_quiet", return_value=probe) as run: make_server.build.detect_cuda_arch() self.assertIsNotNone(run.call_args[1].get("timeout")) class CudaArchArgvTests(unittest.TestCase): """_cuda_arch_argv: the --cuda-arch flags and their status line.""" def _argv(self, backend, arch, emit=None): with patch.object(make_server.build, "detect_cuda_arch", return_value=arch): return make_server.build._cuda_arch_argv(backend, emit=emit) def test_cuda_build_gets_the_detected_arch(self): self.assertEqual(self._argv("cuda", "86"), ["--cuda-arch", "86"]) def test_multi_gpu_arch_passed_verbatim(self): self.assertEqual(self._argv("cuda", "86;89"), ["--cuda-arch", "86;89"]) def test_detection_failure_means_no_flag(self): self.assertEqual(self._argv("cuda", None), []) def test_non_cuda_backends_never_get_the_flag(self): for backend in ("cpu", "vulkan", "hip"): with self.subTest(backend=backend): self.assertEqual(self._argv(backend, "86"), []) def test_status_lines_report_the_outcome(self): detected, emit = [], lambda line: detected.append(line) self._argv("cuda", "86", emit=emit) self.assertIn("CUDA architecture: 86", detected[0]) undetected, emit = [], lambda line: undetected.append(line) self._argv("cuda", None, emit=emit) self.assertIn("portable default list", undetected[0]) self.assertIn(make_server.build.CUDA_ARCH_ENV, undetected[0]) class AudiocppUpdateTests(unittest.TestCase): """update: stop the server, refresh the checkout, rebuild when stale. The rebuild fires when the checkout moved OR the on-disk binary is missing/older than HEAD's commit time (an interrupted earlier build). """ COMMIT_TIME = 1_000_000 def setUp(self): self._td = tempfile.TemporaryDirectory() self.checkout = Path(self._td.name) / "audio.cpp" self.checkout.mkdir() self.addCleanup(common.drain_post_tui_notices) def tearDown(self): self._td.cleanup() def _make_binary(self, backend="cuda"): bin_dir = self.checkout / "build" / f"linux-{backend}-release" / "bin" bin_dir.mkdir(parents=True, exist_ok=True) binary = bin_dir / "audiocpp_server" binary.write_bytes(b"x") return binary def _patch_decision(self, heads, binary, *, commit_time=COMMIT_TIME): """Patch git state + a built binary for BACKEND ("cuda" default). Returns the (mocks) (build, git_update) pair for assertions. BINARY None means no binary on disk (a present binary is stamped newer than COMMIT_TIME — stamp it differently after calling this to simulate staleness); COMMIT_TIME None means the commit-time probe cannot be answered. """ if binary is not None and commit_time is not None: os.utime(binary, (commit_time + 100,) * 2) return patch.object(common, "git_head", side_effect=heads), \ patch.object(common, "git_commit_time", return_value=commit_time), \ patch.object(make_server.build, "load_server_config", return_value={"backend": "cuda"}) def test_no_checkout_is_a_reported_noop(self): with patch.object(make_server.build, "find_local_checkout", return_value=None), \ patch.object(make_server.build.servers, "pid_for", return_value=None), \ patch.object(common, "git_update") as mk_git: rc = make_server.build.update() self.assertEqual(rc, 0) mk_git.assert_not_called() def test_stops_server_then_skips_rebuild_for_a_fresh_binary(self): binary = self._make_binary() patches = self._patch_decision(["a", "a"], binary) with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=1234), \ patch.object(make_server.build.servers, "stop") as mk_stop, \ patches[0], patches[1], patches[2], \ patch.object(common, "git_update", return_value=0) as mk_git, \ patch.object(make_server.build, "build_audiocpp") as mk_build: rc = make_server.build.update(emit="EMIT") self.assertEqual(rc, 0) mk_stop.assert_called_once_with("audiocpp") mk_git.assert_called_once_with(self.checkout, emit="EMIT", cancel=None) # HEAD did not move and the binary is newer than HEAD's commit: # the binary still matches the sources. mk_build.assert_not_called() def test_moved_head_rebuilds_even_with_a_fresh_binary(self): binary = self._make_binary() patches = self._patch_decision(["a", "b"], binary) with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=None), \ patches[0], patches[1], patches[2], \ patch.object(common, "git_update", return_value=0), \ patch.object(make_server.build, "build_audiocpp", return_value=0) as mk_build: rc = make_server.build.update(emit="EMIT") self.assertEqual(rc, 0) mk_build.assert_called_once_with(self.checkout, "cuda", emit="EMIT", cancel=None) def test_moved_head_falls_back_to_the_detected_backend(self): patches = self._patch_decision(["a", "b"], None) with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=None), \ patches[0], patches[1], \ patch.object(make_server.build, "load_server_config", return_value={}), \ patch.object(make_server.build, "detect_backend", return_value="vulkan"), \ patch.object(common, "git_update", return_value=0), \ patch.object(make_server.build, "build_audiocpp", return_value=0) as mk_build: rc = make_server.build.update() self.assertEqual(rc, 0) mk_build.assert_called_once_with(self.checkout, "vulkan", emit=None, cancel=None) def test_no_known_backend_skips_the_rebuild(self): with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=None), \ patch.object(common, "git_head", side_effect=["a", "b"]), \ patch.object(common, "git_update", return_value=0), \ patch.object(make_server.build, "load_server_config", return_value={}), \ patch.object(make_server.build, "detect_backend", return_value=None), \ patch.object(make_server.build, "build_audiocpp") as mk_build: rc = make_server.build.update() self.assertEqual(rc, 0) mk_build.assert_not_called() def test_stale_binary_rebuilds_without_head_movement(self): # The cancelled-rebuild scenario: sources already at HEAD, the old # binary predates the new commit → the next update rebuilds. binary = self._make_binary() patches = self._patch_decision(["a", "a"], binary) os.utime(binary, (self.COMMIT_TIME - 100,) * 2) with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=None), \ patches[0], patches[1], patches[2], \ patch.object(common, "git_update", return_value=0), \ patch.object(make_server.build, "build_audiocpp", return_value=0) as mk_build: rc = make_server.build.update() self.assertEqual(rc, 0) mk_build.assert_called_once_with(self.checkout, "cuda", emit=None, cancel=None) def test_missing_binary_rebuilds_without_head_movement(self): patches = self._patch_decision(["a", "a"], None) with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=None), \ patches[0], patches[1], patches[2], \ patch.object(common, "git_update", return_value=0), \ patch.object(make_server.build, "build_audiocpp", return_value=0) as mk_build: rc = make_server.build.update() self.assertEqual(rc, 0) mk_build.assert_called_once_with(self.checkout, "cuda", emit=None, cancel=None) def test_unknown_commit_time_rebuilds_without_head_movement(self): binary = self._make_binary() patches = self._patch_decision(["a", "a"], binary, commit_time=None) with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=None), \ patches[0], patches[1], patches[2], \ patch.object(common, "git_update", return_value=0), \ patch.object(make_server.build, "build_audiocpp", return_value=0) as mk_build: rc = make_server.build.update() self.assertEqual(rc, 0) mk_build.assert_called_once_with(self.checkout, "cuda", emit=None, cancel=None) def test_checkout_failure_skips_the_rebuild(self): with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=None), \ patch.object(common, "git_update", return_value=128) as mk_git, \ patch.object(make_server.build, "build_audiocpp") as mk_build: rc = make_server.build.update() self.assertEqual(rc, 128) mk_git.assert_called_once() mk_build.assert_not_called() def test_rebuild_failure_propagates_the_exit_code(self): with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=None), \ patch.object(common, "git_head", side_effect=["a", "b"]), \ patch.object(common, "git_update", return_value=0), \ patch.object(make_server.build, "load_server_config", return_value={"backend": "cuda"}), \ patch.object(make_server.build, "build_audiocpp", return_value=2): rc = make_server.build.update() self.assertEqual(rc, 2) def test_cancel_before_the_update_skips_everything_after_stopping(self): cancel = threading.Event() cancel.set() with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=1234), \ patch.object(make_server.build.servers, "stop") as mk_stop, \ patch.object(common, "git_update") as mk_git: rc = make_server.build.update(cancel=cancel) self.assertEqual(rc, 130) mk_stop.assert_called_once_with("audiocpp") mk_git.assert_not_called() def test_cancel_after_the_checkout_skips_the_rebuild(self): cancel = threading.Event() cancel.set() with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=None), \ patch.object(common, "git_head", side_effect=["a", "b"]), \ patch.object(common, "git_update", return_value=0), \ patch.object(make_server.build, "build_audiocpp") as mk_build: rc = make_server.build.update(cancel=cancel) self.assertEqual(rc, 130) mk_build.assert_not_called() class AudiocppDetectTests(unittest.TestCase): """backends.audiocpp.detect() status reporting.""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.root = Path(self._td.name) self.checkout = _make_checkout(self.root) def tearDown(self): self._td.cleanup() def test_not_cloned(self): with patch.object(make_server.build, "find_local_checkout", return_value=None): status = make_server.status.detect() self.assertFalse(status.installed) self.assertFalse(status.configured) self.assertIn("not cloned", status.details[0]) def test_cloned_not_built_not_configured(self): with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout), \ patch.object(make_server.build, "find_audiocpp_server_bin", return_value=None): status = make_server.status.detect() self.assertFalse(status.installed) self.assertFalse(status.configured) self.assertEqual(status.launch_hint, "") self.assertEqual(status.partial, "downloaded (not built)") def test_built_and_configured_ready(self): binary = self.checkout / "build" / "linux-cuda-release" / "bin" \ / "audiocpp_server" binary.parent.mkdir(parents=True) binary.write_bytes(b"x") server_json = self.checkout / "server.json" server_json.write_text('{"models":[]}', encoding="utf-8") with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout): status = make_server.status.detect() self.assertTrue(status.installed) self.assertTrue(status.configured) self.assertIn(str(binary), status.launch_hint) self.assertIn(str(server_json), status.launch_hint) self.assertEqual(status.partial, "") def test_built_not_configured(self): binary = self.checkout / "build" / "linux-cuda-release" / "bin" \ / "audiocpp_server" binary.parent.mkdir(parents=True) binary.write_bytes(b"x") with patch.object(make_server.build, "find_local_checkout", return_value=self.checkout): status = make_server.status.detect() self.assertTrue(status.installed) self.assertFalse(status.configured) self.assertEqual(status.partial, "built (not configured)") class NonInteractiveMainTests(unittest.TestCase): """The flag-only (non-TUI) path through main(), end to end.""" def setUp(self): self._td = tempfile.TemporaryDirectory() self.root = Path(self._td.name) self.folder = self.root / "wavs" self.folder.mkdir() self.output = self.root / "server.json" self.checkout = _make_checkout(self.root) # Isolate config.py rewrites so no test touches the real one. self.fake_config = self.root / "config.py" self.fake_config.write_text(FAKE_CONFIG, encoding="utf-8") patcher = patch.object(make_server.configsync, "CONFIG_PATH", self.fake_config) patcher.start() self.addCleanup(patcher.stop) # Tests run without a tty -> main() takes the non-interactive path. patcher = patch.object(make_server.wizard, "_interactive", return_value=False) patcher.start() self.addCleanup(patcher.stop) def tearDown(self): self._td.cleanup() def _run(self, argv, transcribe=None, whisper="faster_whisper", no_checkout=False): argv = ["backends/audiocpp.py"] + argv transcribe_effect = transcribe if transcribe is not None \ else MagicMock() def detailed(path, model_name="base"): result = transcribe_effect(path, model_name=model_name) if isinstance(result, tuple): return result return (result, "ok" if result else "faster_whisper is not installed (test stub)") with patch.object(sys, "argv", argv), \ patch.object(make_server.build, "find_local_checkout", return_value=None if no_checkout else self.checkout), \ patch.object(make_server.voices, "transcribe_reference_audio_detailed", side_effect=detailed), \ patch.object(make_server.voices, "whisper_backend_problem", return_value=None if whisper else "faster_whisper is not installed"): return make_server.wizard.main() def _args(self, *extra): return ["--wavs", str(self.folder), "--output", str(self.output)] \ + list(extra) def test_default_run_hosts_recommended_entry(self): exit_code = self._run( self._args("--families", "higgs_audio_tts")) self.assertEqual(exit_code, 0) data = json.loads(self.output.read_text(encoding="utf-8")) self.assertEqual(data["host"], "127.0.0.1") self.assertEqual(data["port"], make_server.configsync.config_port()) self.assertEqual(data["backend"], "cuda") self.assertTrue(data["lazy_load"]) self.assertEqual([m["id"] for m in data["models"]], ["Higgs-Audio-v3-TTS-4B-GGUF"]) self.assertNotIn("voice_dir", data) def test_port_comes_from_config_and_leaves_config_alone(self): # Ports are not a wizard question anymore: server.json always # records the port in AUDIOCPP_API_URL (edited in Settings), and # app/converter/config.py itself is never rewritten by setup. with patch.object(config, "AUDIOCPP_API_URL", "http://127.0.0.1:9999"): exit_code = self._run( self._args("--families", "higgs_audio_tts")) self.assertEqual(exit_code, 0) self.assertIn('"http://127.0.0.1:9999"', self.fake_config.read_text(encoding="utf-8")) data = json.loads(self.output.read_text(encoding="utf-8")) self.assertEqual(data["port"], 9999) def test_host_port_sync_flags_removed(self): # No bind-host or port questions anywhere: 127.0.0.1 is fixed and # the port follows Settings, so their flags are gone. parser = make_server.wizard.build_parser() for flag in ("--host", "--port", "--no-sync-port"): with self.assertRaises(SystemExit): parser.parse_args([flag, "x"]) def test_multi_family_lazy_with_voice_dir(self): (self.folder / "narrator.wav").write_bytes(b"x") exit_code = self._run( self._args("--families", "qwen3_tts,higgs_audio_tts"), transcribe=lambda path, model_name="base": "a transcript") self.assertEqual(exit_code, 0) data = json.loads(self.output.read_text(encoding="utf-8")) self.assertEqual([m["id"] for m in data["models"]], ["Qwen3-TTS-12Hz-1.7B-Base-GGUF", "Higgs-Audio-v3-TTS-4B-GGUF"]) self.assertTrue(data["lazy_load"]) self.assertEqual(data["voice_dir"], str(self.folder.resolve())) prompt = (self.folder / common.PROMPT_TEXT_FILENAME).read_text( encoding="utf-8") self.assertIn("narrator|a transcript", prompt) def test_force_overwrites_existing_output(self): self.output.write_text('{"old": true}', encoding="utf-8") exit_code = self._run( self._args("--families", "higgs_audio_tts", "--force")) self.assertEqual(exit_code, 0) data = json.loads(self.output.read_text(encoding="utf-8")) self.assertEqual(len(data["models"]), 1) def test_existing_output_declined_keeps_file(self): self.output.write_text('{"old": true}', encoding="utf-8") exit_code = self._run( self._args("--families", "higgs_audio_tts")) self.assertEqual(exit_code, 1) self.assertEqual(json.loads(self.output.read_text(encoding="utf-8")), {"old": True}) def test_all_packages_hosts_design_as_vdes(self): exit_code = self._run( self._args("--families", "qwen3_tts", "--all-packages")) self.assertEqual(exit_code, 0) data = json.loads(self.output.read_text(encoding="utf-8")) by_id = {m["id"]: m for m in data["models"]} self.assertIn("Qwen3-TTS-12Hz-1.7B-VoiceDesign-GGUF", by_id) self.assertEqual(by_id["Qwen3-TTS-12Hz-1.7B-VoiceDesign-GGUF"]["task"], "vdes") # The non-design packages are hosted with task "tts". self.assertEqual(by_id["Qwen3-TTS-12Hz-1.7B-Base-GGUF"]["task"], "tts") self.assertEqual(by_id["Qwen3-TTS-12Hz-1.7B-CustomVoice-GGUF"]["task"], "tts") def test_unknown_family_rejected(self): with self.assertRaises(SystemExit) as ctx: self._run(self._args("--families", "not_a_family")) self.assertEqual(ctx.exception.code, 2) def test_missing_checkout_rejected(self): with self.assertRaises(SystemExit) as ctx: self._run(["--families", "higgs_audio_tts", "--output", str(self.output)], no_checkout=True) self.assertEqual(ctx.exception.code, 2) def test_missing_wav_dir_rejected(self): missing = self.root / "nope" with self.assertRaises(SystemExit) as ctx: self._run(["--wavs", str(missing), "--output", str(self.output), "--families", "higgs_audio_tts"]) self.assertEqual(ctx.exception.code, 2) def test_families_required_in_noninteractive_run(self): with self.assertRaises(SystemExit) as ctx: self._run(self._args()) self.assertEqual(ctx.exception.code, 2) class FetchServerEndpointsTests(unittest.TestCase): """fetch_server_models / fetch_server_voices: live queries against a running audiocpp_server (urlopen mocked).""" @staticmethod def _urlopen_responding(bodies, errors=None): """A urlopen stub returning successive BODIES (bytes) or raising successive ERRORS; records every requested URL.""" calls = [] def fake_urlopen(url, timeout=10): calls.append(url) if errors: raise errors.pop(0) body = bodies.pop(0) context = MagicMock() context.__enter__.return_value = context context.__exit__.return_value = False context.read.return_value = body return context return fake_urlopen, calls def test_fetch_models_parses_id_family_task(self): urlopen, calls = self._urlopen_responding([json.dumps({ "data": [{"id": "qwen", "family": "qwen3_tts", "task": "tts"}, {"id": "legacy"}], }).encode("utf-8")]) with patch("urllib.request.urlopen", urlopen): models = make_server.remote.fetch_server_models("http://127.0.0.1:8080") # Missing fields mirror the converter's client: empty strings. self.assertEqual(models, [ {"id": "qwen", "family": "qwen3_tts", "task": "tts"}, {"id": "legacy", "family": "", "task": ""}, ]) self.assertEqual(calls, ["http://127.0.0.1:8080/v1/models"]) def test_fetch_models_trailing_slash_url(self): urlopen, calls = self._urlopen_responding( [b'{"data": [{"id": "m"}]}']) with patch("urllib.request.urlopen", urlopen): make_server.remote.fetch_server_models("http://host:8080/") self.assertEqual(calls, ["http://host:8080/v1/models"]) def test_fetch_models_connection_error_returns_none(self): import urllib.error urlopen, _ = self._urlopen_responding( [], errors=[urllib.error.URLError("Connection refused")]) with patch("urllib.request.urlopen", urlopen): self.assertIsNone( make_server.remote.fetch_server_models("http://127.0.0.1:8080")) def test_fetch_models_non_json_body_returns_none(self): # A port answering TCP but not speaking audiocpp_server JSON. urlopen, _ = self._urlopen_responding([b"not json"]) with patch("urllib.request.urlopen", urlopen): self.assertIsNone( make_server.remote.fetch_server_models("http://127.0.0.1:8080")) def test_fetch_models_unexpected_document_yields_empty_list(self): urlopen, _ = self._urlopen_responding([b'{"foo": 1}']) with patch("urllib.request.urlopen", urlopen): self.assertEqual( make_server.remote.fetch_server_models("http://127.0.0.1:8080"), []) def test_fetch_voices_parses_names_and_encodes_model(self): urlopen, calls = self._urlopen_responding( [b'{"voices": ["narrator", "obama"]}']) with patch("urllib.request.urlopen", urlopen): voices = make_server.remote.fetch_server_voices( "http://127.0.0.1:8080", "qwen") self.assertEqual(voices, ["narrator", "obama"]) self.assertEqual(calls, ["http://127.0.0.1:8080/v1/audio/voices?model=qwen"]) def test_fetch_voices_error_returns_none(self): import urllib.error urlopen, _ = self._urlopen_responding( [], errors=[urllib.error.URLError("boom")]) with patch("urllib.request.urlopen", urlopen): self.assertIsNone( make_server.remote.fetch_server_voices("http://h", "qwen")) def test_fetch_voices_non_list_shape_returns_none(self): urlopen, _ = self._urlopen_responding([b'{"voices": 5}']) with patch("urllib.request.urlopen", urlopen): self.assertIsNone( make_server.remote.fetch_server_voices("http://h", "qwen")) class MissingModelEntriesTests(unittest.TestCase): """missing_model_entries: server.json paths vs. files on disk.""" def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.dir = Path(self._tmp.name) def tearDown(self): self._tmp.cleanup() def _server_json(self, models): path = self.dir / "server.json" path.write_text(json.dumps({"models": models}), encoding="utf-8") return path def test_relative_path_resolves_against_config_dir(self): (self.dir / "models" / "present").mkdir(parents=True) (self.dir / "models" / "present" / "m.gguf").write_bytes(b"x") path = self._server_json([ {"id": "a", "path": "models/present"}, {"id": "b", "path": "models/absent"}, ]) missing = make_server.models.missing_model_entries(path) self.assertEqual([m["id"] for m in missing], ["b"]) def test_empty_directory_counts_as_missing(self): (self.dir / "models" / "empty").mkdir(parents=True) path = self._server_json([{"id": "a", "path": "models/empty"}]) self.assertEqual(len(make_server.models.missing_model_entries(path)), 1) def test_absolute_paths_honored(self): target = self.dir / "absolute" target.mkdir() (target / "m.gguf").write_bytes(b"x") path = self._server_json([{"id": "a", "path": str(target)}]) self.assertEqual(make_server.models.missing_model_entries(path), []) def test_unreadable_json_returns_empty(self): path = self.dir / "server.json" path.write_text("not json", encoding="utf-8") self.assertEqual(make_server.models.missing_model_entries(path), []) def test_no_models_returns_empty(self): path = self._server_json([]) self.assertEqual(make_server.models.missing_model_entries(path), []) class ModelInstallHintsTests(unittest.TestCase): """model_install_hints: maps missing paths to the install command.""" def test_maps_path_to_install_id_via_catalog(self): import tempfile with tempfile.TemporaryDirectory() as td: checkout = Path(td) specs = checkout / "model_specs" specs.mkdir() (specs / "qwen3_tts.json").write_text(json.dumps({ "family": "qwen3_tts", "category": "tts", "tasks": ["tts"], "packages": [{ "id": "qwen3_tts_0_6b_base_q8_0", "format": "gguf", "target_directory": "Qwen3-TTS-12Hz-0.6B-Base-GGUF", }], }), encoding="utf-8") missing = [{"id": "qwen", "rel": "models/Qwen3-TTS-12Hz-0.6B-Base-GGUF"}] hints = make_server.models.model_install_hints(checkout, missing) self.assertEqual(len(hints), 1) self.assertIn("qwen3_tts_0_6b_base_q8_0", hints[0]) def test_unmapped_path_names_the_path(self): import tempfile with tempfile.TemporaryDirectory() as td: checkout = Path(td) (checkout / "model_specs").mkdir() hints = make_server.models.model_install_hints( checkout, [{"id": "x", "rel": "models/nope"}]) self.assertIn("models/nope", hints[0]) self.assertNotIn("install", hints[0]) class DetectServerSpecTests(unittest.TestCase): """detect(): the server spec carries the checkout cwd + identity.""" def _checkout(self): tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) checkout = Path(tmp.name) (checkout / "model_specs").mkdir() build = checkout / "build" / "linux-cuda-release" / "bin" build.mkdir(parents=True) (build / "audiocpp_server").write_bytes(b"x") (checkout / "server.json").write_text(json.dumps({ "models": [{"id": "qwen", "family": "qwen3_tts", "path": "models/Qwen3-TTS-12Hz-0.6B-Base-GGUF"}], }), encoding="utf-8") return checkout def test_spec_has_cwd_and_identity(self): checkout = self._checkout() with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(make_server.status, "_detect_remote", return_value=(False, {})): status = make_server.status.detect() self.assertEqual(len(status.servers), 1) spec = status.servers[0] self.assertEqual(spec.cwd, checkout) self.assertEqual(spec.identity, "audiocpp") self.assertIn("--config", spec.argv) def test_models_missing_flag_and_details(self): checkout = self._checkout() with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(make_server.status, "_detect_remote", return_value=(False, {})): status = make_server.status.detect() self.assertTrue(status.models_missing) self.assertTrue(any("not downloaded" in line for line in status.details)) class InstalledModelEntriesTests(unittest.TestCase): """installed_model_entries: the complement of missing_model_entries.""" def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.dir = Path(self._tmp.name) def tearDown(self): self._tmp.cleanup() def _server_json(self, models): path = self.dir / "server.json" path.write_text(json.dumps({"models": models}), encoding="utf-8") return path def test_lists_entries_whose_files_are_on_disk(self): (self.dir / "models" / "present").mkdir(parents=True) (self.dir / "models" / "present" / "m.gguf").write_bytes(b"x") path = self._server_json([ {"id": "a", "path": "models/present"}, {"id": "b", "path": "models/absent"}, ]) installed = make_server.models.installed_model_entries(path) self.assertEqual([m["id"] for m in installed], ["a"]) def test_unreadable_json_returns_empty(self): path = self.dir / "server.json" path.write_text("not json", encoding="utf-8") self.assertEqual(make_server.models.installed_model_entries(path), []) class MissingModelInstallGuidanceTests(unittest.TestCase): """missing_model_install_guidance: missing paths -> (id, install_id).""" def test_maps_paths_and_skips_unmapped(self): with tempfile.TemporaryDirectory() as td: checkout = Path(td) specs = checkout / "model_specs" specs.mkdir() (specs / "qwen3_tts.json").write_text(json.dumps({ "family": "qwen3_tts", "category": "tts", "tasks": ["tts"], "packages": [{ "id": "qwen3_tts_0_6b_base_q8_0", "format": "gguf", "target_directory": "Qwen3-TTS-12Hz-0.6B-Base-GGUF", }], }), encoding="utf-8") missing = [ {"id": "qwen", "rel": "models/Qwen3-TTS-12Hz-0.6B-Base-GGUF"}, {"id": "x", "rel": "models/nope"}, ] guidance = make_server.models.missing_model_install_guidance( checkout, missing) self.assertEqual(guidance, [("qwen", "qwen3_tts_0_6b_base_q8_0")]) class LoadServerConfigTests(unittest.TestCase): """load_server_config: read server.json, or None when unusable.""" def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.dir = Path(self._tmp.name) def tearDown(self): self._tmp.cleanup() def test_reads_dict_document(self): path = self.dir / "server.json" path.write_text(json.dumps({"host": "0.0.0.0", "models": []}), encoding="utf-8") self.assertEqual(make_server.catalog.load_server_config(path), {"host": "0.0.0.0", "models": []}) def test_missing_file_returns_none(self): self.assertIsNone(make_server.catalog.load_server_config( self.dir / "nope.json")) def test_unreadable_json_returns_none(self): path = self.dir / "server.json" path.write_text("not json", encoding="utf-8") self.assertIsNone(make_server.catalog.load_server_config(path)) def test_non_dict_document_returns_none(self): path = self.dir / "server.json" path.write_text("[1, 2, 3]", encoding="utf-8") self.assertIsNone(make_server.catalog.load_server_config(path)) class ServerConfigSelectionsTests(unittest.TestCase): """server_config_selections: map server.json models back to the catalog.""" def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.checkout = _make_checkout(Path(self._tmp.name)) self.catalog = make_server.catalog.load_model_catalog(self.checkout) def tearDown(self): self._tmp.cleanup() def test_maps_paths_to_family_dirs_and_tasks(self): config = {"models": [ {"id": "qwen", "family": "qwen3_tts", "path": "models/Qwen3-TTS-12Hz-1.7B-Base-GGUF", "task": "tts"}, {"id": "qwen-design", "family": "qwen3_tts", "path": "models/Qwen3-TTS-12Hz-1.7B-VoiceDesign-GGUF", "task": "vdes"}, {"id": "higgs", "family": "higgs_audio_tts", "path": "models/Higgs-Audio-v3-TTS-4B-GGUF", "task": "tts"}, ]} selected, tasks = make_server.catalog.server_config_selections(config, self.catalog) self.assertEqual(selected["qwen3_tts"], ["Qwen3-TTS-12Hz-1.7B-Base-GGUF", "Qwen3-TTS-12Hz-1.7B-VoiceDesign-GGUF"]) self.assertEqual(selected["higgs_audio_tts"], ["Higgs-Audio-v3-TTS-4B-GGUF"]) self.assertEqual(tasks[("qwen3_tts", "Qwen3-TTS-12Hz-1.7B-VoiceDesign-GGUF")], "vdes") self.assertEqual(tasks[("qwen3_tts", "Qwen3-TTS-12Hz-1.7B-Base-GGUF")], "tts") def test_unknown_family_ignored(self): config = {"models": [ {"id": "x", "family": "not_a_family", "path": "models/x"}, ]} selected, tasks = make_server.catalog.server_config_selections(config, self.catalog) self.assertEqual(selected, {}) self.assertEqual(tasks, {}) def test_absolute_and_unprefixed_paths_kept_as_targets(self): config = {"models": [ {"id": "qwen", "family": "qwen3_tts", "path": "/abs/Qwen3-TTS-12Hz-1.7B-Base-GGUF", "task": "tts"}, ]} selected, tasks = make_server.catalog.server_config_selections(config, self.catalog) self.assertEqual(selected["qwen3_tts"], ["/abs/Qwen3-TTS-12Hz-1.7B-Base-GGUF"]) def test_empty_models_yield_empty_selections(self): selected, tasks = make_server.catalog.server_config_selections({"models": []}, self.catalog) self.assertEqual(selected, {}) self.assertEqual(tasks, {}) class UnusedInstalledEntriesTests(unittest.TestCase): """unused_installed_entries: installed models dropped by a new selection.""" def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.dir = Path(self._tmp.name) (self.dir / "models" / "kept").mkdir(parents=True) (self.dir / "models" / "kept" / "m.gguf").write_bytes(b"x") (self.dir / "models" / "dropped").mkdir() (self.dir / "models" / "dropped" / "m.gguf").write_bytes(b"x") (self.dir / "models" / "missing").mkdir() # empty: not installed def tearDown(self): self._tmp.cleanup() def _server_json(self, models): path = self.dir / "server.json" path.write_text(json.dumps({"models": models}), encoding="utf-8") return path def test_returns_installed_entries_not_in_new_paths(self): path = self._server_json([ {"id": "kept", "path": "models/kept"}, {"id": "dropped", "path": "models/dropped"}, {"id": "missing", "path": "models/missing"}, ]) unused = make_server.models.unused_installed_entries( path, {"models/kept"}) self.assertEqual([entry["id"] for entry in unused], ["dropped"]) def test_nothing_unused_when_all_kept(self): path = self._server_json([ {"id": "kept", "path": "models/kept"}, ]) unused = make_server.models.unused_installed_entries( path, {"models/kept"}) self.assertEqual(unused, []) class DeleteModelFilesTests(unittest.TestCase): """delete_model_files: remove on-disk model files for {id, rel} entries.""" def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.dir = Path(self._tmp.name) (self.dir / "models" / "a").mkdir(parents=True) (self.dir / "models" / "a" / "m.gguf").write_bytes(b"x") (self.dir / "models" / "b").mkdir() (self.dir / "models" / "b" / "m.gguf").write_bytes(b"x") (self.dir / "models" / "c").mkdir(parents=True) self.server_json = self.dir / "server.json" self.server_json.write_text(json.dumps({ "models": [ {"id": "a", "path": "models/a"}, {"id": "b", "path": "models/b"}, {"id": "c", "path": "models/c"}, ], }), encoding="utf-8") def tearDown(self): self._tmp.cleanup() def test_removes_dirs_and_counts(self): removed = make_server.models.delete_model_files( self.server_json, [{"id": "a", "rel": "models/a"}, {"id": "b", "rel": "models/b"}]) self.assertEqual(removed, 2) self.assertFalse((self.dir / "models" / "a").exists()) self.assertFalse((self.dir / "models" / "b").exists()) self.assertTrue((self.dir / "models" / "c").exists()) def test_missing_paths_ignored(self): removed = make_server.models.delete_model_files( self.server_json, [{"id": "ghost", "rel": "models/ghost"}]) self.assertEqual(removed, 0) def test_removes_single_file(self): file_path = self.dir / "models" / "single.gguf" file_path.write_bytes(b"x") removed = make_server.models.delete_model_files( self.server_json, [{"id": "s", "rel": "models/single.gguf"}]) self.assertEqual(removed, 1) self.assertFalse(file_path.exists()) def test_absolute_rel_path_honored(self): target = self.dir / "absolute" target.mkdir() (target / "m.gguf").write_bytes(b"x") removed = make_server.models.delete_model_files( self.server_json, [{"id": "a", "rel": str(target)}]) self.assertEqual(removed, 1) self.assertFalse(target.exists()) class InstallModelsApiTests(unittest.TestCase): """install_models: runs the install helper with download=True.""" def test_downloads_delegating_to_install_models(self): with tempfile.TemporaryDirectory() as td: checkout = Path(td) guidance = [("qwen", "qwen3_tts_0_6b_base_q8_0")] with patch.object(make_server.models, "_install_models") as mk: make_server.models.install_models(checkout, guidance) mk.assert_called_once_with(checkout, guidance, download=True, emit=None, cancel=None) class HandInstallGuidanceTests(unittest.TestCase): """hand_install_guidance: explains how to install models by hand.""" def test_lists_each_model_and_its_path(self): with tempfile.TemporaryDirectory() as td: checkout = Path(td) message = make_server.models.hand_install_guidance(checkout, [ {"id": "qwen", "rel": "models/Qwen3-TTS-12Hz-0.6B-Base-GGUF"}, {"id": "higgs", "rel": "models/Higgs-Audio-4B-GGUF"}, ]) self.assertIn("qwen", message) self.assertIn("models/Qwen3-TTS-12Hz-0.6B-Base-GGUF", message) self.assertIn("higgs", message) self.assertIn("models/Higgs-Audio-4B-GGUF", message) self.assertIn("download", message.lower()) class WizardNavigationTests(unittest.TestCase): """Esc in the audio.cpp wizard goes back one screen (via tui.Wizard).""" def _args(self): return make_server.wizard.build_parser().parse_args([]) def _checkout(self): tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) return _make_checkout(Path(tmp.name)) def test_esc_on_first_screen_aborts(self): # Configure audio.cpp (modify flow): the families tree is the first # screen, so Esc on it must abort the wizard — not re-show itself. checkout = self._checkout() with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(tui, "checkbox_tree", return_value=make_server.wizard._GO_BACK): settings = make_server.wizard._wizard(None, self._args(), make_server.wizard.build_parser()) self.assertIsNone(settings) def test_modify_flow_offers_build_when_not_built(self): # A server.json recording "vulkan" exists, but nothing is built: the # combined config form must still ask the backend (pre-selecting # vulkan) and offer the build — instead of silently skipping it # because the existing server.json already records a backend. checkout = self._checkout() (checkout / "server.json").write_text( json.dumps({"models": [], "backend": "vulkan"}), encoding="utf-8") catalog = make_server.catalog.load_model_catalog(checkout) supertonic = next(i for i, entry in enumerate(catalog) if entry["family"] == "supertonic") def fake_tree(*args, **kwargs): return [(supertonic, "Supertonic-GGUF")] captured = {} def fake_form(stdscr, title, fields, **kwargs): captured["title"] = title captured["keys"] = [f["key"] for f in fields] by_key = {f["key"]: f for f in fields} return {f["key"]: f["value"] for f in fields} | { "backend": by_key["backend"]["value"], "build": False, # decline the build } with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(tui, "checkbox_tree", side_effect=fake_tree), \ patch.object(tui, "form", side_effect=fake_form): settings = make_server.wizard._wizard(None, self._args(), make_server.wizard.build_parser()) self.assertIsNotNone(settings) self.assertEqual(settings["backend"], "vulkan") self.assertFalse(settings["build"]) # The config screen is one combined form (not one question per # screen) that includes both the backend pick and the build offer. self.assertEqual(captured["title"], "Configure audio.cpp") self.assertIn("backend", captured["keys"]) self.assertIn("build", captured["keys"]) def test_esc_on_config_form_returns_to_families_tree(self): # Esc on the combined config form must fall back to the model-family # tree; re-selecting then proceeds through the rest of the wizard. checkout = self._checkout() catalog = make_server.catalog.load_model_catalog(checkout) supertonic = next(i for i, entry in enumerate(catalog) if entry["family"] == "supertonic") tree_calls = [] form_calls = [] def fake_tree(*args, **kwargs): tree_calls.append(1) return [(supertonic, "Supertonic-GGUF")] def fake_form(stdscr, title, fields, **kwargs): form_calls.append(title) if len(form_calls) == 1: return tui.Wizard.BACK # Esc on the config form return {f["key"]: f["value"] for f in fields} with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(tui, "checkbox_tree", side_effect=fake_tree), \ patch.object(tui, "form", side_effect=fake_form): settings = make_server.wizard._wizard(None, self._args(), make_server.wizard.build_parser()) self.assertIsNotNone(settings) # The tree was re-shown after the form's Esc. self.assertEqual(len(tree_calls), 2) self.assertEqual(form_calls, ["Configure audio.cpp", "Configure audio.cpp"]) self.assertEqual([m["id"] for m in settings["model_entries"]], ["Supertonic-GGUF"]) def test_combined_form_defaults_and_fixed_host_port(self): # One screen collects everything: the form value defaults produce a # complete settings dict whose host/port never came from questions. checkout = self._checkout() catalog = make_server.catalog.load_model_catalog(checkout) supertonic = next(i for i, entry in enumerate(catalog) if entry["family"] == "supertonic") def fake_tree(*args, **kwargs): return [(supertonic, "Supertonic-GGUF")] def fake_form(stdscr, title, fields, **kwargs): return {f["key"]: f["value"] for f in fields} with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(tui, "checkbox_tree", side_effect=fake_tree), \ patch.object(tui, "form", side_effect=fake_form): settings = make_server.wizard._wizard(None, self._args(), make_server.wizard.build_parser()) self.assertIsNotNone(settings) self.assertEqual(settings["host"], "127.0.0.1") self.assertEqual(settings["port"], make_server.configsync.config_port()) self.assertEqual(settings["backend"], "cuda") # default choice self.assertTrue(settings["build"]) # not built yet → offered (default Yes) self.assertFalse(settings["download"]) # no manager script here def test_tree_screen_starts_on_confirm(self): # The model-tree screen opens with focus on Confirm so Enter # accepts the seeded/checked selection immediately. checkout = self._checkout() catalog = make_server.catalog.load_model_catalog(checkout) qwen3 = next(i for i, entry in enumerate(catalog) if entry["family"] == "qwen3_tts") captured = {} def fake_tree(*args, **kwargs): captured.update(kwargs) return [(qwen3, "Qwen3-TTS-12Hz-1.7B-Base-GGUF")] def fake_form(stdscr, title, fields, **kwargs): return {f["key"]: f["value"] for f in fields} with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(tui, "checkbox_tree", side_effect=fake_tree), \ patch.object(tui, "form", side_effect=fake_form): make_server.wizard._wizard(None, self._args(), make_server.wizard.build_parser()) self.assertTrue(captured.get("start_on_buttons")) def test_wav_dir_seeded_from_existing_voice_dir(self): # A modify run loads the Voice clone .wav directory from the # server.json being configured instead of starting blank. checkout = self._checkout() recorded_voices = checkout.parent / "recorded-voices" (checkout / "server.json").write_text(json.dumps({ "host": "127.0.0.1", "port": 8080, "backend": "cuda", "models": [], "voice_dir": str(recorded_voices), }), encoding="utf-8") catalog = make_server.catalog.load_model_catalog(checkout) qwen3 = next(i for i, entry in enumerate(catalog) if entry["family"] == "qwen3_tts") def fake_tree(*args, **kwargs): return [(qwen3, "Qwen3-TTS-12Hz-1.7B-Base-GGUF")] def fake_form(stdscr, title, fields, **kwargs): by_key = {f["key"]: f for f in fields} self.assertEqual(by_key["wav_dir"]["value"], Path(recorded_voices)) # The directory browser alerts on the .wavs it lists. self.assertIs(by_key["wav_dir"]["info"], common.wav_dir_info) self.assertIs(by_key["wav_dir"]["preview"], common.wav_dir_preview) return {f["key"]: f["value"] for f in fields} with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(tui, "checkbox_tree", side_effect=fake_tree), \ patch.object(tui, "form", side_effect=fake_form): make_server.wizard._wizard(None, self._args(), make_server.wizard.build_parser()) def test_wav_dir_defaults_to_project_voices_when_unconfigured(self): # Without a voice_dir in server.json the field starts on the # project's voices/ directory — never blank. checkout = self._checkout() (checkout / "server.json").write_text( json.dumps({"models": []}), encoding="utf-8") catalog = make_server.catalog.load_model_catalog(checkout) qwen3 = next(i for i, entry in enumerate(catalog) if entry["family"] == "qwen3_tts") def fake_tree(*args, **kwargs): return [(qwen3, "Qwen3-TTS-12Hz-1.7B-Base-GGUF")] def fake_form(stdscr, title, fields, **kwargs): by_key = {f["key"]: f for f in fields} self.assertEqual(by_key["wav_dir"]["value"], common.VOICES_DIR) return {f["key"]: f["value"] for f in fields} with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(tui, "checkbox_tree", side_effect=fake_tree), \ patch.object(tui, "form", side_effect=fake_form): make_server.wizard._wizard(None, self._args(), make_server.wizard.build_parser()) def test_build_offer_hidden_when_backend_already_built(self): # A checkout with a built binary for the chosen backend must not # show (or honor) a build offer. checkout = self._checkout() catalog = make_server.catalog.load_model_catalog(checkout) supertonic = next(i for i, entry in enumerate(catalog) if entry["family"] == "supertonic") binary = checkout / "build" / "linux-cuda-release" / "bin" \ / "audiocpp_server" binary.parent.mkdir(parents=True) binary.write_bytes(b"x") def fake_tree(*args, **kwargs): return [(supertonic, "Supertonic-GGUF")] def fake_form(stdscr, title, fields, **kwargs): keys = [f["key"] for f in fields] self.assertNotIn("build", keys) self.assertNotIn("backend", keys) return {f["key"]: f["value"] for f in fields} with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(tui, "checkbox_tree", side_effect=fake_tree), \ patch.object(tui, "form", side_effect=fake_form): settings = make_server.wizard._wizard(None, self._args(), make_server.wizard.build_parser()) self.assertIsNotNone(settings) self.assertFalse(settings["build"]) self.assertEqual(settings["backend"], "cuda") class UninstallTests(unittest.TestCase): """uninstall: stop the server and remove the checkout.""" def test_removes_checkout(self): with tempfile.TemporaryDirectory() as td: checkout = Path(td) / "audio.cpp" checkout.mkdir() with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(servers, "pid_for", return_value=1234), \ patch.object(servers, "stop") as mk_stop: rc = make_server.build.uninstall() self.assertEqual(rc, 0) self.assertFalse(checkout.exists()) mk_stop.assert_called_once_with("audiocpp") def test_skips_stop_without_a_pid_file(self): # No pid file: the server was never started by this tool, so # stop (and its "stop it manually" noise) is skipped. with patch.object(make_server.build, "find_local_checkout", return_value=None), \ patch.object(servers, "pid_for", return_value=None), \ patch.object(servers, "stop") as mk_stop: rc = make_server.build.uninstall() self.assertEqual(rc, 0) mk_stop.assert_not_called() def test_accepts_task_view_kwargs_for_registry_symmetry(self): # The hub calls uninstall(emit=..., cancel=...); emit is unused here # (no subprocess phase) and cancel=None behaves like the plain call. with tempfile.TemporaryDirectory() as td: checkout = Path(td) / "audio.cpp" checkout.mkdir() with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(servers, "pid_for", return_value=1234), \ patch.object(servers, "stop"): rc = make_server.build.uninstall(emit=lambda line: None, cancel=None) self.assertEqual(rc, 0) self.assertFalse(checkout.exists()) def test_cancel_before_delete_keeps_checkout(self): # Cancel is honored between phases only: once the server is stopped # and cancellation is pending, the checkout deletion never starts. with tempfile.TemporaryDirectory() as td: checkout = Path(td) / "audio.cpp" checkout.mkdir() cancel = threading.Event() cancel.set() with patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(servers, "pid_for", return_value=1234), \ patch.object(servers, "stop"): rc = make_server.build.uninstall(cancel=cancel) self.assertEqual(rc, 130) self.assertTrue(checkout.exists()) if __name__ == "__main__": unittest.main() class SetupScreenTests(unittest.TestCase): """setup_screen: the wizard run on the hub's screen, setup tail via the in-TUI task view (two parallel lanes on a fresh install).""" def test_abort_returns_one_without_executing(self): with patch.object(make_server.wizard, "_wizard", return_value=None) as mk_wizard, \ patch.object(make_server.wizard, "_execute_lanes") as mk_lanes: rc = make_server.wizard.setup_screen(None) self.assertEqual(rc, 1) mk_wizard.assert_called_once() mk_lanes.assert_not_called() def test_success_runs_the_tail_in_the_task_view(self): settings = {"audiocpp_dir": Path("/x")} lanes = [taskview.TaskLane( "Build", [taskview.TaskStep("t", lambda emit, cancel: 0)])] with patch.object(make_server.wizard, "_wizard", return_value=settings), \ patch.object(make_server.wizard, "_execute_lanes", return_value=lanes) as mk_lanes, \ patch.object(taskview, "run_lanes", return_value=0) as mk_run: rc = make_server.wizard.setup_screen(None) self.assertEqual(rc, 0) mk_lanes.assert_called_once() self.assertIs(mk_lanes.call_args[0][0], settings) mk_run.assert_called_once() self.assertEqual(mk_run.call_args[0][2], lanes) class ExecuteLanesTests(unittest.TestCase): """_execute_lanes: two lanes (build + configure/download) and the flattened console order.""" def _settings(self, **overrides): settings = { "audiocpp_dir": Path("/x"), "backend": "cuda", "build": True, "download": True, "include_clone": False, "wav_dir": None, "plan": None, "sync_port": None, "delete_unused": False, "unused_entries": [], "model_entries": [], "entry_ids": [], "install_guidance": [], "output_path": Path("/x/server.json"), "host": "127.0.0.1", "port": 8080, "lazy_load": True, } settings.update(overrides) return settings def test_two_lanes_when_building(self): args = make_server.wizard.build_parser().parse_args([]) lanes = make_server.wizard._execute_lanes(self._settings(), args) self.assertEqual([lane.title for lane in lanes], ["Build", "Configure & download"]) self.assertEqual([s.title for s in lanes[0].steps], ["Build audiocpp_server (cuda)"]) self.assertEqual([s.title for s in lanes[1].steps], ["Transcribe reference voices", "Write server.json & sync config", "Download models"]) def test_single_lane_when_not_building(self): args = make_server.wizard.build_parser().parse_args([]) lanes = make_server.wizard._execute_lanes( self._settings(build=False), args) self.assertEqual([lane.title for lane in lanes], ["Configure & download"]) def test_flattened_console_steps_keep_the_build_first(self): args = make_server.wizard.build_parser().parse_args([]) steps = make_server.wizard._execute_steps(self._settings(), args) self.assertEqual([s.title for s in steps], ["Build audiocpp_server (cuda)", "Transcribe reference voices", "Write server.json & sync config", "Download models"]) def test_download_step_prints_the_launch_hint(self): args = make_server.wizard.build_parser().parse_args([]) lanes = make_server.wizard._execute_lanes(self._settings(), args) install_step = lanes[1].steps[2] with patch.object(make_server.models, "_install_models"), \ patch.object(make_server.build, "_print_launch_hint") as mk_hint: install_step.work(lambda line: None, threading.Event()) mk_hint.assert_called_once_with(Path("/x"), Path("/x/server.json")) class LaunchHintTests(unittest.TestCase): """_print_launch_hint: silent when built, remediation when not.""" def _capture(self, audiocpp_dir, output_path, binary=None): buf = io.StringIO() with redirect_stdout(buf), \ patch.object(make_server.build, "find_audiocpp_server_bin", return_value=binary): make_server.build._print_launch_hint(audiocpp_dir, output_path) return buf.getvalue() def test_built_server_prints_nothing(self): # The hub starts/stops the server itself; no manual instructions. out = self._capture(Path("/tmp/acpp"), Path("/tmp/acpp/server.json"), binary=Path("/tmp/acpp/build/x/bin/audiocpp_server")) self.assertEqual(out, "") def test_missing_binary_gives_build_remediation(self): out = self._capture(Path("/tmp/acpp"), Path("/tmp/acpp/server.json")) self.assertIn("Build it first", out) self.assertNotIn("Start the server with:", out) def _checkout_with_script(self, script_name): td = tempfile.TemporaryDirectory() self.addCleanup(td.cleanup) checkout = Path(td.name) scripts = checkout / "scripts" scripts.mkdir() (scripts / script_name).write_text("#!/bin/sh\n", encoding="utf-8") return checkout def test_linux_hint_uses_bash_and_backend_flag(self): checkout = self._checkout_with_script("build_linux.sh") script = checkout / "scripts" / "build_linux.sh" out = self._capture(checkout, checkout / "server.json") self.assertIn(f"bash {script} --backend " "--target audiocpp_server --deployment-build", out) def test_darwin_hint_uses_metal_without_backend(self): checkout = self._checkout_with_script("build_metal.sh") script = checkout / "scripts" / "build_metal.sh" with patch("sys.platform", "darwin"): out = self._capture(checkout, checkout / "server.json") self.assertIn(f"bash {script} --target audiocpp_server " "--deployment-build", out) self.assertNotIn("--backend", out) def test_windows_hint_names_powershell_and_presets(self): checkout = self._checkout_with_script("build_windows.ps1") script = checkout / "scripts" / "build_windows.ps1" with patch("sys.platform", "win32"): out = self._capture(checkout, checkout / "server.json") self.assertIn(f"powershell -NoProfile -ExecutionPolicy Bypass " f"-File {script} -Preset windows-cuda-release " "-Target audiocpp_server -DeploymentBuild", out) self.assertIn("windows-cpu-release", out) self.assertIn("build_windows_hip.ps1", out) def _asset(name, size=1234, digest=None): """A GitHub release asset entry shaped like the API returns.""" return {"name": name, "size": size, "digest": digest, "browser_download_url": f"https://example.test/{name}"} def _release(assets, tag="v9.9.9"): return {"tag_name": tag, "assets": assets} def _sha256_file(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() def _make_tar_gz(tmp: Path, name: str, entries: dict, mode: int = 0o755) -> Path: archive = tmp / name with tarfile.open(archive, "w:gz") as tf: for member, content in entries.items(): data = content.encode("utf-8") info = tarfile.TarInfo(member) info.size = len(data) info.mode = mode tf.addfile(info, io.BytesIO(data)) return archive def _make_zip(tmp: Path, name: str, entries: dict) -> Path: archive = tmp / name with zipfile.ZipFile(archive, "w") as zf: for member, content in entries.items(): zf.writestr(member, content) return archive class PrebuiltSelectTests(unittest.TestCase): """Asset selection for the prebuilt release downloads.""" ASSETS = [ _asset("audio-v9.9.9-bin-macos-arm64-metal.tar.gz"), _asset("audio-v9.9.9-bin-macos-x64-metal.tar.gz"), _asset("audio-v9.9.9-bin-windows-x64-cpu.zip"), _asset("audio-v9.9.9-bin-windows-x64-vulkan.zip"), _asset("audio-v9.9.9-bin-windows-x64-cuda12.4.zip"), _asset("audio-v9.9.9-bin-windows-x64-cuda13.3.zip"), _asset("audio-v9.9.9-cudart-windows-x64-cuda12.4.zip"), _asset("audio-v9.9.9-cudart-windows-x64-cuda13.3.zip"), _asset("audio-v9.9.9-bin-ubuntu-x64-cpu.tar.gz"), ] def test_darwin_arm64_selects_the_metal_tarball(self): pair = make_server.prebuilt.select_assets( self.ASSETS, "cpu", platform="darwin", machine="arm64") self.assertEqual( pair[0]["name"], "audio-v9.9.9-bin-macos-arm64-metal.tar.gz") self.assertIsNone(pair[1]) def test_darwin_x64_selects_its_own_tarball(self): pair = make_server.prebuilt.select_assets( self.ASSETS, "cpu", platform="darwin", machine="x86_64") self.assertEqual( pair[0]["name"], "audio-v9.9.9-bin-macos-x64-metal.tar.gz") def test_darwin_has_no_cuda_asset(self): self.assertIsNone(make_server.prebuilt.select_assets( self.ASSETS, "cuda", platform="darwin", machine="arm64")) def test_windows_selects_cpu_and_vulkan_zips(self): for backend, name in (("cpu", "audio-v9.9.9-bin-windows-x64-cpu.zip"), ("vulkan", "audio-v9.9.9-bin-windows-x64-vulkan.zip")): pair = make_server.prebuilt.select_assets( self.ASSETS, backend, platform="win32", machine="AMD64") self.assertEqual(pair[0]["name"], name) self.assertIsNone(pair[1]) def test_windows_cuda_pairs_the_binary_with_its_cudart(self): pair = make_server.prebuilt.select_assets( self.ASSETS, "cuda", platform="win32", machine="AMD64", cuda_variant="12.4") self.assertEqual(pair[0]["name"], "audio-v9.9.9-bin-windows-x64-cuda12.4.zip") self.assertEqual(pair[1]["name"], "audio-v9.9.9-cudart-windows-x64-cuda12.4.zip") def test_windows_cuda_without_cudart_asset_is_rejected(self): assets = [_asset("audio-v9.9.9-bin-windows-x64-cuda12.4.zip")] self.assertIsNone(make_server.prebuilt.select_assets( assets, "cuda", platform="win32", machine="AMD64", cuda_variant="12.4")) def test_windows_hip_has_no_asset(self): self.assertIsNone(make_server.prebuilt.select_assets( self.ASSETS, "hip", platform="win32", machine="AMD64")) def test_windows_arm64_has_no_asset(self): self.assertIsNone(make_server.prebuilt.select_assets( self.ASSETS, "cpu", platform="win32", machine="arm64")) def test_linux_never_selects(self): self.assertIsNone(make_server.prebuilt.select_assets( self.ASSETS, "cuda", platform="linux", machine="x86_64")) def test_prebuilt_supported_matrix(self): supported = make_server.prebuilt.prebuilt_supported self.assertTrue(supported("cpu", platform="darwin")) self.assertFalse(supported("cuda", platform="darwin")) self.assertTrue(supported("cpu", platform="win32")) self.assertTrue(supported("vulkan", platform="win32")) self.assertTrue(supported("cuda", platform="win32")) self.assertFalse(supported("hip", platform="win32")) self.assertFalse(supported("cuda", platform="linux")) def test_default_cuda_variant_follows_the_driver(self): def driver(version): proc = MagicMock() proc.returncode = 0 proc.stdout = f"{version}\n".encode("utf-8") return proc with patch.object(make_server.prebuilt.common, "run_console_subprocess_quiet", return_value=driver("580.82.07")): self.assertEqual( make_server.prebuilt._default_cuda_variant(), "13.3") with patch.object(make_server.prebuilt.common, "run_console_subprocess_quiet", return_value=driver("579.10")): self.assertEqual( make_server.prebuilt._default_cuda_variant(), "12.4") with patch.object(make_server.prebuilt.common, "run_console_subprocess_quiet", return_value=None): self.assertEqual( make_server.prebuilt._default_cuda_variant(), "12.4") class PrebuiltInstallTests(unittest.TestCase): """install_prebuilt: download, verify, extract, mark.""" def _checkout(self) -> Path: tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) return _make_checkout(Path(tmp.name)) def _marker(self, checkout: Path, backend: str) -> Path: return make_server.prebuilt.marker_path(checkout, backend, platform="darwin") def _install_darwin(self, checkout: Path, *, digest: str, asset_name="audio-v9.9.9-bin-macos-x64-metal.tar.gz", archive=None): if archive is None: archive = _make_tar_gz( Path(self.tmp.name), asset_name, {"./audiocpp_server": "#!/bin/sh\n", "./tools/model_manager_v2.py": "# tool\n"}) asset = _asset(asset_name, size=archive.stat().st_size, digest=f"sha256:{digest}") release = _release([asset]) def fake_download(url, dest, *, emit=None, cancel=None): self.assertEqual(url, asset["browser_download_url"]) shutil.copyfile(archive, dest) return 0 with patch("sys.platform", "darwin"), \ patch.object(make_server.prebuilt, "fetch_latest_release", return_value=release), \ patch.object(make_server.prebuilt, "_download", side_effect=fake_download) as mk_dl, \ patch.object(make_server.prebuilt.common, "run_console_subprocess", return_value=0): rc = make_server.prebuilt.install_prebuilt(checkout, "cpu") return rc, mk_dl def setUp(self): tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) self.tmp = tmp def test_darwin_install_extracts_and_marks(self): checkout = self._checkout() archive = _make_tar_gz( Path(self.tmp.name), "audio-v9.9.9-bin-macos-x64-metal.tar.gz", {"./audiocpp_server": "#!/bin/sh\n", "./tools/model_manager_v2.py": "# tool\n"}, mode=0o644) rc, _ = self._install_darwin( checkout, digest=_sha256_file(archive), archive=archive) self.assertEqual(rc, 0) bin_dir = checkout / "build" / "macos-metal-release" / "bin" server = bin_dir / "audiocpp_server" self.assertTrue(server.exists()) # GitHub's artifact round-trip loses the tar permission bits; the # install must restore the exec bit the hub relies on to launch. self.assertTrue(server.stat().st_mode & 0o111) self.assertTrue((bin_dir / "tools" / "model_manager_v2.py").exists()) marker = json.loads( (checkout / "build" / "macos-metal-release" / "prebuilt.json") .read_text(encoding="utf-8")) self.assertEqual(marker["tag"], "v9.9.9") self.assertEqual(marker["asset"], "audio-v9.9.9-bin-macos-x64-metal.tar.gz") def test_install_replaces_a_previous_build_dir(self): checkout = self._checkout() stale = checkout / "build" / "macos-metal-release" / "bin" stale.mkdir(parents=True) (stale / "junk.txt").write_text("stale", encoding="utf-8") archive = _make_tar_gz( Path(self.tmp.name), "audio-v9.9.9-bin-macos-x64-metal.tar.gz", {"./audiocpp_server": "#!/bin/sh\n"}) rc, _ = self._install_darwin( checkout, digest=_sha256_file(archive), archive=archive) self.assertEqual(rc, 0) self.assertFalse((stale / "junk.txt").exists()) self.assertTrue((stale / "audiocpp_server").exists()) def test_reinstall_of_the_same_release_is_a_noop(self): checkout = self._checkout() archive = _make_tar_gz( Path(self.tmp.name), "audio-v9.9.9-bin-macos-x64-metal.tar.gz", {"./audiocpp_server": "#!/bin/sh\n"}) rc, _ = self._install_darwin( checkout, digest=_sha256_file(archive), archive=archive) self.assertEqual(rc, 0) rc, mk_dl = self._install_darwin( checkout, digest=_sha256_file(archive), archive=archive) self.assertEqual(rc, 0) mk_dl.assert_not_called() def test_checksum_mismatch_aborts_without_installing(self): checkout = self._checkout() rc, _ = self._install_darwin(checkout, digest="sha256:" + "0" * 64) self.assertEqual(rc, 1) self.assertFalse( (checkout / "build" / "macos-metal-release" / "bin" / "audiocpp_server").exists()) self.assertIsNone( make_server.prebuilt.installed_release(checkout, "cpu")) def test_windows_cuda_installs_both_zips_into_the_preset_dir(self): checkout = self._checkout() main = _make_zip( Path(self.tmp.name), "audio-v9.9.9-bin-windows-x64-cuda13.3.zip", {"audiocpp_server.exe": "MZ", "ggml-cuda.dll": "MZ"}) cudart = _make_zip( Path(self.tmp.name), "audio-v9.9.9-cudart-windows-x64-cuda13.3.zip", {"cudart64_13.dll": "MZ"}) release = _release([ _asset(main.name, size=main.stat().st_size, digest=f"sha256:{_sha256_file(main)}"), _asset(cudart.name, size=cudart.stat().st_size, digest=f"sha256:{_sha256_file(cudart)}"), ]) archives = {f"https://example.test/{p.name}": p for p in (main, cudart)} def fake_download(url, dest, *, emit=None, cancel=None): shutil.copyfile(archives[url], dest) return 0 with patch("sys.platform", "win32"), \ patch.object(make_server.prebuilt, "fetch_latest_release", return_value=release), \ patch.object(make_server.prebuilt, "_download", side_effect=fake_download), \ patch.object(make_server.prebuilt.common, "run_console_subprocess", return_value=0): rc = make_server.prebuilt.install_prebuilt( checkout, "cuda", cuda_variant="13.3") self.assertEqual(rc, 0) bin_dir = checkout / "build" / "windows-cuda-release" / "bin" self.assertTrue((bin_dir / "audiocpp_server.exe").exists()) self.assertTrue((bin_dir / "cudart64_13.dll").exists()) self.assertEqual(make_server.prebuilt.installed_release( checkout, "cuda", platform="win32")["tag"], "v9.9.9") def test_unsupported_backend_fails_loudly(self): checkout = self._checkout() with patch("sys.platform", "linux"): rc = make_server.prebuilt.install_prebuilt(checkout, "cuda") self.assertEqual(rc, 1) def test_marker_roundtrip_and_garbage(self): checkout = self._checkout() self.assertIsNone(make_server.prebuilt.installed_release( checkout, "cpu", platform="darwin")) marker = self._marker(checkout, "cpu") marker.parent.mkdir(parents=True) marker.write_text(json.dumps({"tag": "v1.2.3"}), encoding="utf-8") self.assertEqual(make_server.prebuilt.installed_release( checkout, "cpu", platform="darwin")["tag"], "v1.2.3") marker.write_text("not json", encoding="utf-8") self.assertIsNone(make_server.prebuilt.installed_release( checkout, "cpu", platform="darwin")) def test_install_dir_mirrors_the_source_build_layout(self): checkout = Path("/tmp/acpp") self.assertEqual( make_server.prebuilt.install_dir(checkout, "cpu", platform="darwin"), checkout / "build" / "macos-metal-release" / "bin") self.assertEqual( make_server.prebuilt.install_dir(checkout, "cuda", platform="win32"), checkout / "build" / "windows-cuda-release" / "bin") self.assertIsNone( make_server.prebuilt.install_dir(checkout, "hip", platform="win32")) self.assertIsNone( make_server.prebuilt.install_dir(checkout, "cpu", platform="linux")) class PrebuiltUpdateRoutingTests(unittest.TestCase): """update() routes prebuilt installs to a release re-download.""" def _checkout(self) -> Path: tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) return _make_checkout(Path(tmp.name)) def _mark_prebuilt(self, checkout: Path, tag: str) -> None: marker = make_server.prebuilt.marker_path(checkout, "cpu", platform="darwin") marker.parent.mkdir(parents=True) (marker.parent / "bin").mkdir(exist_ok=True) marker.write_text(json.dumps({"tag": tag, "asset": "x"}), encoding="utf-8") def _marker(self, checkout: Path) -> Path: return make_server.prebuilt.marker_path(checkout, "cpu", platform="darwin") def _run(self, checkout: Path, *, marker: Optional[dict], release: Optional[dict], tag: Optional[str] = None, install_rc: int = 0, build_rc: int = 0): # TAG is what the quota-free redirect resolution reports; RELEASE # is the API fallback (only consulted when the redirect fails). # INSTALL_RC is what the prebuilt re-download reports: 0 (or a # cancellation) returns directly, a failure falls back to the # source-build route, whose BUILD_RC decides the final exit code. with patch("sys.platform", "darwin"), \ patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=None), \ patch.object(make_server.build, "load_server_config", return_value={"backend": "cpu"}), \ patch.object(make_server.prebuilt, "installed_release", return_value=marker), \ patch.object(make_server.prebuilt, "resolve_latest_tag", return_value=tag) as mk_tag, \ patch.object(make_server.prebuilt, "fetch_latest_release", return_value=release) as mk_fetch, \ patch.object(make_server.prebuilt, "install_prebuilt", return_value=install_rc) as mk_install, \ patch.object(common, "git_update", return_value=0) as mk_git, \ patch.object(make_server.build, "build_audiocpp", return_value=build_rc) as mk_build: rc = make_server.build.update() return rc, mk_install, mk_git, mk_tag, mk_fetch, mk_build def test_newer_release_triggers_a_redownload(self): checkout = self._checkout() self._mark_prebuilt(checkout, "v0.6.0") rc, mk_install, mk_git, _mk_tag, mk_fetch, mk_build = self._run( checkout, marker={"tag": "v0.6.0", "asset": "x"}, release=_release([], tag="v0.7.0"), tag="v0.7.0") self.assertEqual(rc, 0) mk_git.assert_not_called() mk_install.assert_called_once_with(checkout, "cpu", emit=None, cancel=None) mk_fetch.assert_not_called() mk_build.assert_not_called() def test_current_release_is_a_noop(self): checkout = self._checkout() rc, mk_install, mk_git, _mk_tag, mk_fetch, mk_build = self._run( checkout, marker={"tag": "v0.7.0", "asset": "x"}, release=None, tag="v0.7.0") self.assertEqual(rc, 0) mk_install.assert_not_called() mk_git.assert_not_called() mk_build.assert_not_called() # The redirect already answered: the API (rate-limited easily) # must not be touched for an "already current" check. mk_fetch.assert_not_called() def test_unreachable_github_keeps_the_install(self): checkout = self._checkout() rc, mk_install, mk_git, mk_tag, mk_fetch, mk_build = self._run( checkout, marker={"tag": "v0.7.0", "asset": "x"}, release=None, tag=None) self.assertEqual(rc, 0) mk_tag.assert_called_once() mk_fetch.assert_called_once() # the API fallback tried too mk_install.assert_not_called() mk_git.assert_not_called() mk_build.assert_not_called() def test_failed_redownload_falls_back_to_a_source_build(self): checkout = self._checkout() self._mark_prebuilt(checkout, "v0.6.0") rc, mk_install, mk_git, _mk_tag, _mk_fetch, mk_build = self._run( checkout, marker={"tag": "v0.6.0", "asset": "x"}, release=_release([], tag="v0.7.0"), tag="v0.7.0", install_rc=1) self.assertEqual(rc, 0) mk_install.assert_called_once_with(checkout, "cpu", emit=None, cancel=None) mk_git.assert_called_once() mk_build.assert_called_once_with(checkout, "cpu", emit=None, cancel=None) # The fallback source build replaces the prebuilt install: the # marker must go, or the next update re-downloads over the # freshly built binary. self.assertFalse(self._marker(checkout).exists()) def test_failed_redownload_keeps_the_marker_when_the_build_fails(self): # A failed fallback leaves the previous prebuilt binary in place # (install_prebuilt downloads before touching it), so the marker # stays truthful and the next update retries the re-download. checkout = self._checkout() self._mark_prebuilt(checkout, "v0.6.0") rc, _mk_install, mk_git, _mk_tag, _mk_fetch, mk_build = self._run( checkout, marker={"tag": "v0.6.0", "asset": "x"}, release=_release([], tag="v0.7.0"), tag="v0.7.0", install_rc=1, build_rc=1) self.assertEqual(rc, 1) mk_git.assert_called_once() mk_build.assert_called_once() self.assertTrue(self._marker(checkout).exists()) def test_cancelled_redownload_does_not_fall_back(self): checkout = self._checkout() self._mark_prebuilt(checkout, "v0.6.0") rc, _mk_install, mk_git, _mk_tag, _mk_fetch, mk_build = self._run( checkout, marker={"tag": "v0.6.0", "asset": "x"}, release=_release([], tag="v0.7.0"), tag="v0.7.0", install_rc=130) self.assertEqual(rc, 130) mk_git.assert_not_called() mk_build.assert_not_called() self.assertTrue(self._marker(checkout).exists()) def test_source_builds_still_route_through_git(self): checkout = self._checkout() (checkout / "server.json").write_text( json.dumps({"models": [], "backend": "cpu"}), encoding="utf-8") with patch("sys.platform", "darwin"), \ patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(make_server.build.servers, "pid_for", return_value=None), \ patch.object(make_server.prebuilt, "installed_release", return_value=None), \ patch.object(common, "git_update", return_value=0) as mk_git, \ patch.object(make_server.build, "build_audiocpp", return_value=0) as mk_build: rc = make_server.build.update() self.assertEqual(rc, 0) mk_git.assert_called_once() mk_build.assert_called_once_with(checkout, "cpu", emit=None, cancel=None) class PrebuiltFlagTests(unittest.TestCase): """--prebuilt resolves into a concrete install mode.""" def _args(self, value): return make_server.wizard.build_parser().parse_args( ["--prebuilt", value] if value else []) def test_auto_downloads_on_macos_and_windows(self): with patch("sys.platform", "darwin"): self.assertEqual( make_server.wizard._flag_build_mode(self._args("auto"), "cpu"), "prebuilt") with patch("sys.platform", "win32"): self.assertEqual( make_server.wizard._flag_build_mode(self._args("auto"), "cuda"), "prebuilt") self.assertEqual( make_server.wizard._flag_build_mode(self._args("auto"), "hip"), "source") def test_auto_builds_from_source_on_linux(self): self.assertEqual( make_server.wizard._flag_build_mode(self._args("auto"), "cuda"), "source") def test_yes_and_no_force_their_modes(self): self.assertEqual( make_server.wizard._flag_build_mode(self._args("yes"), "hip"), "prebuilt") self.assertEqual( make_server.wizard._flag_build_mode(self._args("no"), "cpu"), "source") def test_darwin_wizard_honors_the_form_mode(self): # On macOS the combined form asks "Get audiocpp_server" as a # three-way choice; the answer must drive the install mode. tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) checkout = _make_checkout(Path(tmp.name)) catalog = make_server.catalog.load_model_catalog(checkout) qwen_index = next(i for i, entry in enumerate(catalog) if entry["family"] == "qwen3_tts") def fake_tree(*args, **kwargs): return [(qwen_index, "Qwen3-TTS-12Hz-1.7B-Base-GGUF")] def fake_form(stdscr, title, fields, **kwargs): by_key = {f["key"]: f for f in fields} return {f["key"]: f["value"] for f in fields} | { "backend": by_key["backend"]["value"], "build_mode": mode, } for mode, expected_build in (("skip", False), ("source", True), ("prebuilt", True)): with patch("sys.platform", "darwin"), \ patch.object(make_server.build, "find_local_checkout", return_value=checkout), \ patch.object(tui, "checkbox_tree", side_effect=fake_tree), \ patch.object(tui, "form", side_effect=fake_form): settings = make_server.wizard._wizard( None, make_server.wizard.build_parser().parse_args([]), make_server.wizard.build_parser()) self.assertIsNotNone(settings) self.assertEqual(settings["build_mode"], mode) self.assertEqual(settings["build"], expected_build) class PrebuiltTagResolutionTests(unittest.TestCase): """resolve_latest_tag: the quota-free release-page redirect.""" def _redirect(self, url): response = MagicMock() response.__enter__.return_value.geturl.return_value = url return response def test_parses_the_redirect_target(self): with patch("urllib.request.urlopen", return_value=self._redirect( "https://github.com/0xShug0/audio.cpp/releases/tag/" "v0.7.0")): self.assertEqual(make_server.prebuilt.resolve_latest_tag(), "v0.7.0") def test_offline_yields_none(self): with patch("urllib.request.urlopen", side_effect=OSError("down")): self.assertIsNone(make_server.prebuilt.resolve_latest_tag()) def test_unexpected_page_yields_none(self): with patch("urllib.request.urlopen", return_value=self._redirect( "https://github.com/0xShug0/audio.cpp/releases")): self.assertIsNone(make_server.prebuilt.resolve_latest_tag()) class PrebuiltSyntheticAssetTests(unittest.TestCase): """synthesize_assets: API-free names for the rate-limited fallback.""" def test_darwin_names_match_select_assets(self): tag = "v1.2.3" selected = make_server.prebuilt.select_assets( [_asset(f"audio-{tag}-bin-macos-arm64-metal.tar.gz")], "cpu", platform="darwin", machine="arm64") synth = make_server.prebuilt.synthesize_assets( "cpu", tag, platform="darwin", machine="arm64") self.assertEqual(selected[0]["name"], synth[0]["name"]) self.assertEqual( synth[0]["browser_download_url"], f"https://github.com/0xShug0/audio.cpp/releases/download/" f"{tag}/audio-{tag}-bin-macos-arm64-metal.tar.gz") self.assertIsNone(synth[1]) def test_windows_cuda_synthesizes_both_archives(self): synth = make_server.prebuilt.synthesize_assets( "cuda", "v1.2.3", platform="win32", machine="AMD64", cuda_variant="13.3") main, extra = synth self.assertEqual(main["name"], "audio-v1.2.3-bin-windows-x64-cuda13.3.zip") self.assertEqual(extra["name"], "audio-v1.2.3-cudart-windows-x64-cuda13.3.zip") self.assertIsNone(main["digest"]) def test_unsupported_backend_is_none(self): self.assertIsNone(make_server.prebuilt.synthesize_assets( "hip", "v1.2.3", platform="win32", machine="AMD64")) class PrebuiltRateLimitTests(unittest.TestCase): """install_prebuilt degrades when the GitHub API is rate-limited.""" def setUp(self): tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) self.tmp = tmp def _checkout(self) -> Path: tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) return _make_checkout(Path(tmp.name)) def test_rate_limited_api_still_installs_unverified(self): checkout = self._checkout() archive = _make_tar_gz( Path(self.tmp.name), "audio-v9.9.9-bin-macos-x64-metal.tar.gz", {"./audiocpp_server": "#!/bin/sh\n"}) def fake_download(url, dest, *, emit=None, cancel=None): self.assertIn("/releases/download/v9.9.9/", url) shutil.copyfile(archive, dest) return 0 with patch("sys.platform", "darwin"), \ patch.object(make_server.prebuilt, "fetch_latest_release", return_value=None), \ patch.object(make_server.prebuilt, "resolve_latest_tag", return_value="v9.9.9"), \ patch.object(make_server.prebuilt, "_download", side_effect=fake_download) as mk_dl, \ patch.object(make_server.prebuilt.common, "run_console_subprocess", return_value=0), \ redirect_stdout(io.StringIO()) as out: rc = make_server.prebuilt.install_prebuilt(checkout, "cpu") self.assertEqual(rc, 0) mk_dl.assert_called_once() server = (checkout / "build" / "macos-metal-release" / "bin" / "audiocpp_server") self.assertTrue(server.exists()) self.assertTrue(server.stat().st_mode & 0o111) marker = make_server.prebuilt.installed_release(checkout, "cpu", platform="darwin") self.assertEqual(marker["tag"], "v9.9.9") self.assertIn("WITHOUT checksum verification", out.getvalue()) def test_total_outage_fails_the_install(self): checkout = self._checkout() with patch("sys.platform", "darwin"), \ patch.object(make_server.prebuilt, "fetch_latest_release", return_value=None), \ patch.object(make_server.prebuilt, "resolve_latest_tag", return_value=None): rc = make_server.prebuilt.install_prebuilt(checkout, "cpu") self.assertEqual(rc, 1) self.assertIsNone(make_server.prebuilt.installed_release( checkout, "cpu", platform="darwin")) def test_reachable_api_without_a_matching_asset_is_a_hard_error(self): # A reachable API with no matching asset must not be papered over # with a synthesized name that would just 404. checkout = self._checkout() with patch("sys.platform", "darwin"), \ patch.object(make_server.prebuilt, "fetch_latest_release", return_value=_release([], tag="v9.9.9")), \ patch.object(make_server.prebuilt, "resolve_latest_tag", return_value="v9.9.9") as mk_tag: rc = make_server.prebuilt.install_prebuilt(checkout, "cpu") self.assertEqual(rc, 1) mk_tag.assert_not_called() class PrebuiltFallbackTests(unittest.TestCase): """A failed prebuilt download falls back to a source build in-lane.""" def _settings(self, checkout: Path, **overrides) -> dict: settings = { "audiocpp_dir": checkout, "backend": "cpu", "build": True, "build_mode": "prebuilt", "prebuilt_forced": False, "wav_dir": None, "include_clone": False, "plan": None, "output_path": checkout / "server.json", "model_entries": [], "install_guidance": [], "host": "127.0.0.1", "port": 8080, "lazy_load": True, "download": False, "delete_unused": False, "unused_entries": [], } settings.update(overrides) return settings def _build_step(self, checkout: Path, settings: dict): args = argparse.Namespace(input_dir=None, whisper_model="base") lanes = make_server.wizard._execute_lanes(settings, args) return lanes[0].steps[0] def _run_step(self, step, *, out): with redirect_stdout(out): return step.work(None, None) def _checkout(self) -> Path: tmp = tempfile.TemporaryDirectory() self.addCleanup(tmp.cleanup) return _make_checkout(Path(tmp.name)) def test_failed_download_falls_back_to_source_build(self): checkout = self._checkout() step = self._build_step(checkout, self._settings(checkout)) with patch.object(make_server.prebuilt, "install_prebuilt", return_value=1) as mk_install, \ patch.object(make_server.build, "build_audiocpp", return_value=0) as mk_build, \ redirect_stdout(io.StringIO()) as out: rc = self._run_step(step, out=out) self.assertEqual(rc, 0) mk_install.assert_called_once() mk_build.assert_called_once_with(checkout, "cpu", emit=None, cancel=None) self.assertIn("falling back to a source build", out.getvalue()) def test_both_paths_failing_still_reports_an_error(self): checkout = self._checkout() step = self._build_step(checkout, self._settings(checkout)) with patch.object(make_server.prebuilt, "install_prebuilt", return_value=1), \ patch.object(make_server.build, "build_audiocpp", return_value=3), \ redirect_stdout(io.StringIO()): rc = self._run_step(step, out=io.StringIO()) self.assertEqual(rc, 3) def test_cancelled_download_does_not_fall_back(self): checkout = self._checkout() step = self._build_step(checkout, self._settings(checkout)) with patch.object(make_server.prebuilt, "install_prebuilt", return_value=130) as _mk_install, \ patch.object(make_server.build, "build_audiocpp") as mk_build: rc = self._run_step(step, out=io.StringIO()) self.assertEqual(rc, 130) mk_build.assert_not_called() def test_forced_prebuilt_fails_fast(self): checkout = self._checkout() step = self._build_step( checkout, self._settings(checkout, prebuilt_forced=True)) with patch.object(make_server.prebuilt, "install_prebuilt", return_value=1) as _mk_install, \ patch.object(make_server.build, "build_audiocpp") as mk_build: rc = self._run_step(step, out=io.StringIO()) self.assertEqual(rc, 1) mk_build.assert_not_called() def test_source_mode_never_touches_the_download(self): checkout = self._checkout() step = self._build_step( checkout, self._settings(checkout, build_mode="source")) with patch.object(make_server.prebuilt, "install_prebuilt") as mk_i, \ patch.object(make_server.build, "build_audiocpp", return_value=0) as mk_build: rc = self._run_step(step, out=io.StringIO()) self.assertEqual(rc, 0) mk_i.assert_not_called() mk_build.assert_called_once() class MissingStripPrefixSanitizeTests(unittest.TestCase): """The missing-strip_prefix repair for nested single-GGUF packages.""" def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.checkout_holder = Path(self._tmp.name) / "audio.cpp" self.checkout_holder.mkdir() def tearDown(self): self._tmp.cleanup() _GGUF_SPEC = { "family": "glm_tts", "sources": [{"format": "gguf", "roots": {"model": ".", "weights": "$gguf"}}], } def _spec(self, files, roots=(("model", "."), ("weights", "$gguf")), fmt="gguf"): spec = json.loads(json.dumps(self._GGUF_SPEC)) spec["sources"][0]["roots"] = dict(roots) spec["sources"][0]["format"] = fmt spec["packages"] = [{"id": "pkg", "format": fmt, "files": files}] return spec def test_nested_single_gguf_gets_its_prefix(self): spec = self._spec(["Text to audio (TTS)/GLM-TTS_Q8.gguf"]) self.assertTrue(make_server.catalog.sanitize_model_spec(spec)) self.assertEqual(spec["packages"][0]["strip_prefix"], "Text to audio (TTS)") def test_explicit_gguf_paths_without_gguf_root_are_untouched(self): # minimax_music3-style: the gguf source names tensors by explicit # file paths (no $gguf root), so the nested layout is intentional. spec = self._spec(["config/a.json", "tokenizer/t.json", "language_model_q4_0.gguf"], roots=(("model", "."),)) self.assertFalse(make_server.catalog.sanitize_model_spec(spec)) def test_mixed_prefixes_are_untouched(self): spec = self._spec(["config/a.json", "model.safetensors"]) self.assertFalse(make_server.catalog.sanitize_model_spec(spec)) def test_flat_package_is_untouched(self): spec = self._spec(["model.gguf"]) self.assertFalse(make_server.catalog.sanitize_model_spec(spec)) def test_safetensors_packages_are_untouched(self): spec = self._spec(["Some-Dir/model.safetensors"], fmt="safetensors") self.assertFalse(make_server.catalog.sanitize_model_spec(spec)) def test_catalog_carries_the_sanitized_prefix(self): _write_spec(self.checkout_holder, "glm_like", packages=[{ "id": "glm_like_q8_0", "default": True, "format": "gguf", "target_directory": "GLM-Like-Q8", "files": ["Text to audio (TTS)/GLM-Like_Q8.gguf"], }]) specs_dir = self.checkout_holder / "model_specs" spec = json.loads( (specs_dir / "glm_like.json").read_text(encoding="utf-8")) spec["sources"] = [{"format": "gguf", "roots": {"model": ".", "weights": "$gguf"}}] (specs_dir / "glm_like.json").write_text( json.dumps(spec), encoding="utf-8") catalog = make_server.catalog.load_model_catalog(self.checkout_holder) glm_like = next(e for e in catalog if e["family"] == "glm_like") self.assertEqual(glm_like["packages"][0]["strip_prefix"], "Text to audio (TTS)") self.assertEqual( make_server.catalog.entry_model_path(glm_like), "models/GLM-Like-Q8") class EntryModelPathTests(unittest.TestCase): """entry_model_path: directory hosting vs. the multi-GGUF file rule.""" def _entry(self, packages, default_directory=None): if default_directory is None and packages: default_directory = str( packages[0].get("target_directory") or "Bundle-GGUF") return {"family": "minimax_h3", "packages": packages, "default_path": f"models/{default_directory or ''}"} def test_multi_gguf_package_is_hosted_from_its_first_gguf(self): entry = self._entry([{ "id": "minimax_h3_q4_k", "default": True, "format": "gguf", "target_directory": "MiniMax-H3-Q4-GGUF", "strip_prefix": "MiniMax-H3-Q4-GGUF", "files": [ "MiniMax-H3-Q4-GGUF/configuration.json", "MiniMax-H3-Q4-GGUF/text_encoder_q4_k.gguf", "MiniMax-H3-Q4-GGUF/dit.gguf", "MiniMax-H3-Q4-GGUF/audio_vae_folded_f16.gguf", "MiniMax-H3-Q4-GGUF/video_vae.gguf", ], }]) self.assertEqual( make_server.catalog.entry_model_path(entry), "models/MiniMax-H3-Q4-GGUF/text_encoder_q4_k.gguf") def test_single_gguf_package_hosts_the_directory(self): entry = self._entry([{ "id": "voxcpm2_q8_0", "default": True, "format": "gguf", "target_directory": "VoxCPM2-GGUF", "strip_prefix": "VoxCPM2-GGUF", "files": ["VoxCPM2-GGUF/voxcpm2-q8_0.gguf"], }], default_directory="VoxCPM2-GGUF") self.assertEqual(make_server.catalog.entry_model_path(entry), "models/VoxCPM2-GGUF") def test_alternate_directory_uses_that_packages_files(self): entry = self._entry([ {"id": "a_q8", "default": True, "format": "gguf", "target_directory": "A-GGUF", "files": ["A-GGUF/a.gguf", "A-GGUF/b.gguf"]}, {"id": "b_q8", "format": "gguf", "target_directory": "B-GGUF", "files": ["B-GGUF/b.gguf"]}, ]) self.assertEqual(make_server.catalog.entry_model_path(entry, "B-GGUF"), "models/B-GGUF") def test_safetensors_directory_hosts_the_directory(self): entry = self._entry([{ "id": "voxcpm2_safetensors", "format": "safetensors", "target_directory": "VoxCPM2", "files": ["config.json", "model.safetensors"], }], default_directory="VoxCPM2") self.assertEqual(make_server.catalog.entry_model_path(entry), "models/VoxCPM2") def test_entry_without_packages_falls_back_to_the_directory(self): entry = self._entry([], default_directory="Fallback-GGUF") self.assertEqual(make_server.catalog.entry_model_path(entry), "models/Fallback-GGUF") class BuildModelEntrySessionOptionsTests(unittest.TestCase): """build_model_entry carries per-entry session options when given.""" def test_session_options_added_when_given(self): entry = make_server.catalog.build_model_entry( "miotts", "MioTTS-1.7B-GGUF", "models/MioTTS-1.7B-GGUF", session_options={"miotts.codec_model_path": "models/MioCodec"}) self.assertEqual(entry["session_options"], {"miotts.codec_model_path": "models/MioCodec"}) def test_session_options_omitted_when_empty(self): entry = make_server.catalog.build_model_entry( "miotts", "MioTTS-1.7B-GGUF", "models/MioTTS-1.7B-GGUF") self.assertNotIn("session_options", entry) class FilePrecisePresenceTests(unittest.TestCase): """Installed checks are file-precise against the catalog packages.""" def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.checkout = Path(self._tmp.name) / "audio.cpp" self.checkout.mkdir() specs = self.checkout / "model_specs" specs.mkdir() spec = { "family": "glm_like", "category": "tts", "tasks": ["tts", "clone"], "packages": [{ "id": "glm_like_q8_0", "default": True, "format": "gguf", "target_directory": "GLM-Like-Q8", "files": ["Text to audio (TTS)/GLM-Like_Q8.gguf"], }], "sources": [{"format": "gguf", "roots": {"model": ".", "weights": "$gguf"}}], } (specs / "glm_like.json").write_text(json.dumps(spec), encoding="utf-8") def tearDown(self): self._tmp.cleanup() def _entry(self, rel="models/GLM-Like-Q8"): return {"id": "GLM-Like-Q8", "family": "glm_like", "path": rel} def test_stale_nested_layout_counts_as_missing(self): stale = self.checkout / "models" / "GLM-Like-Q8" \ / "Text to audio (TTS)" stale.mkdir(parents=True) (stale / "GLM-Like_Q8.gguf").write_bytes(b"x") self.assertFalse( make_server.models._all_models_present(self.checkout, [self._entry()])) server_json = self.checkout / "server.json" server_json.write_text(json.dumps({"models": [self._entry()]}), encoding="utf-8") missing = make_server.models.missing_model_entries(server_json) self.assertEqual([m["id"] for m in missing], ["GLM-Like-Q8"]) def test_flat_layout_after_the_repair_counts_as_installed(self): target = self.checkout / "models" / "GLM-Like-Q8" target.mkdir(parents=True) (target / "GLM-Like_Q8.gguf").write_bytes(b"x") self.assertTrue( make_server.models._all_models_present(self.checkout, [self._entry()])) server_json = self.checkout / "server.json" server_json.write_text(json.dumps({"models": [self._entry()]}), encoding="utf-8") self.assertEqual( make_server.models.missing_model_entries(server_json), []) def test_file_style_entry_of_a_multi_gguf_package(self): specs = self.checkout / "model_specs" spec = { "family": "multi", "category": "tts", "tasks": ["tts"], "packages": [{ "id": "multi_q4", "default": True, "format": "gguf", "target_directory": "Multi-Q4-GGUF", "strip_prefix": "Multi-Q4-GGUF", "files": ["Multi-Q4-GGUF/dit.gguf", "Multi-Q4-GGUF/vae.gguf"], }], } (specs / "multi.json").write_text(json.dumps(spec), encoding="utf-8") target = self.checkout / "models" / "Multi-Q4-GGUF" target.mkdir(parents=True) (target / "dit.gguf").write_bytes(b"x") (target / "vae.gguf").write_bytes(b"x") self.assertTrue(make_server.models._all_models_present( self.checkout, [{"id": "Multi", "path": "models/Multi-Q4-GGUF/dit.gguf"}])) def test_unmatched_entry_keeps_the_plain_path_check(self): target = self.checkout / "elsewhere" target.mkdir() (target / "m.gguf").write_bytes(b"x") self.assertTrue(make_server.models._all_models_present( self.checkout, [{"id": "x", "path": str(target)}])) class CompanionInstallTests(unittest.TestCase): """Companion packages (MioCodec for MioTTS) join the install list.""" def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.checkout = Path(self._tmp.name) / "audio.cpp" self.checkout.mkdir() specs = self.checkout / "model_specs" specs.mkdir() manager = self.checkout / "tools" / "model_manager_v2.py" manager.parent.mkdir() manager.write_text("#!/usr/bin/env python3\n", encoding="utf-8") (specs / "miocodec.json").write_text(json.dumps({ "family": "miocodec", "category": "audio_tools", "tasks": ["codec"], "packages": [{ "id": "miocodec_q8_0", "default": True, "format": "gguf", "target_directory": "MioCodec-25Hz-44.1kHz-v2-GGUF", "strip_prefix": "MioCodec-25Hz-44.1kHz-v2-GGUF", "files": ["MioCodec-25Hz-44.1kHz-v2-GGUF/codec.gguf"], }], }), encoding="utf-8") def tearDown(self): self._tmp.cleanup() def test_missing_companion_is_merged_into_pending(self): merged = make_server.models._merge_companions( self.checkout, [], [("MioCodec", "miocodec_q8_0")]) self.assertEqual(merged, [("MioCodec", "miocodec_q8_0")]) def test_installed_companion_is_reported_and_skipped(self): target = self.checkout / "models" / "MioCodec-25Hz-44.1kHz-v2-GGUF" target.mkdir(parents=True) (target / "codec.gguf").write_bytes(b"x") buf = io.StringIO() with redirect_stdout(buf): merged = make_server.models._merge_companions( self.checkout, [], [("MioCodec", "miocodec_q8_0")]) self.assertEqual(merged, []) self.assertIn("MioCodec is already installed", buf.getvalue()) def test_install_models_prints_the_companion_command(self): buf = io.StringIO() with redirect_stdout(buf): rc = make_server.models._install_models( self.checkout, [], download=False, companions=[("MioCodec", "miocodec_q8_0")]) self.assertEqual(rc, 0) self.assertIn("install miocodec_q8_0", buf.getvalue()) def test_install_models_runs_the_companion_download(self): buf = io.StringIO() with redirect_stdout(buf), \ patch.object(common, "run_console_subprocess", return_value=0) as run: rc = make_server.models._install_models( self.checkout, [], download=True, companions=[("MioCodec", "miocodec_q8_0")]) self.assertEqual(rc, 0) argv = run.call_args[0][0] self.assertEqual(argv[-2:], ["install", "miocodec_q8_0"]) class ApplyEntrySessionOptionsTests(unittest.TestCase): """The wizard bakes companion/session options into server.json entries.""" def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.checkout = Path(self._tmp.name) / "audio.cpp" self.checkout.mkdir() self.wav_dir = Path(self._tmp.name) / "voices" self.wav_dir.mkdir() def tearDown(self): self._tmp.cleanup() def _write_wav(self, name, seconds, rate=16000): import wave as wave_mod with wave_mod.open(str(self.wav_dir / name), "wb") as handle: handle.setnchannels(1) handle.setsampwidth(2) handle.setframerate(rate) handle.writeframes(b"\x00\x00" * int(rate * seconds)) def test_miotts_entry_gets_the_codec_model_path(self): entries = [{"id": "MioTTS-1.7B-GGUF", "family": "miotts"}] applied = make_server.catalog.apply_entry_session_options( entries, None, self.checkout) self.assertEqual(applied, ["MioTTS-1.7B-GGUF"]) self.assertEqual( entries[0]["session_options"]["miotts.codec_model_path"], "models/MioCodec-25Hz-44.1kHz-v2-GGUF") def test_hand_set_codec_path_is_not_overridden(self): entries = [{"id": "MioTTS-1.7B-GGUF", "family": "miotts", "session_options": {"miotts.codec_model_path": "/custom/codec"}}] applied = make_server.catalog.apply_entry_session_options( entries, None, self.checkout) self.assertEqual(applied, []) self.assertEqual( entries[0]["session_options"]["miotts.codec_model_path"], "/custom/codec") def test_voxcpm_capacity_sized_to_the_longest_voice(self): self._write_wav("short.wav", 5) self._write_wav("long.wav", 40) entries = [{"id": "VoxCPM2-GGUF", "family": "voxcpm2"}] applied = make_server.catalog.apply_entry_session_options( entries, self.wav_dir, self.checkout) self.assertEqual(applied, ["VoxCPM2-GGUF"]) self.assertEqual( entries[0]["session_options"] ["voxcpm2.audiovae_encoder_sample_capacity"], "720000") def test_short_voices_need_no_capacity(self): self._write_wav("short.wav", 5) entries = [{"id": "VoxCPM2-GGUF", "family": "voxcpm2"}] applied = make_server.catalog.apply_entry_session_options( entries, self.wav_dir, self.checkout) self.assertEqual(applied, []) self.assertNotIn("session_options", entries[0]) def test_no_voice_directory_means_no_capacity(self): entries = [{"id": "VoxCPM2-GGUF", "family": "voxcpm2"}] make_server.catalog.apply_entry_session_options(entries, None, self.checkout) self.assertNotIn("session_options", entries[0]) def test_existing_session_options_are_preserved(self): entries = [{"id": "VoxCPM2-GGUF", "family": "voxcpm2", "session_options": {"voxcpm2.mem_saver": "true"}}] self._write_wav("long.wav", 40) make_server.catalog.apply_entry_session_options( entries, self.wav_dir, self.checkout) options = entries[0]["session_options"] self.assertEqual(options["voxcpm2.mem_saver"], "true") self.assertIn("voxcpm2.audiovae_encoder_sample_capacity", options) class FilePathSelectionTests(unittest.TestCase): """Selections and unused-model matching for file-hosted entries.""" def test_file_style_entry_maps_to_its_directory(self): config = {"models": [ {"id": "MiniMax-H3-Q4-GGUF", "family": "minimax_h3", "path": "models/MiniMax-H3-Q4-GGUF/text_encoder_q4_k.gguf", "task": "tts"}, ]} catalog = [{"family": "minimax_h3", "packages": [], "default_path": "models/MiniMax-H3-Q4-GGUF"}] selected, tasks = make_server.catalog.server_config_selections( config, catalog) self.assertEqual(selected["minimax_h3"], ["MiniMax-H3-Q4-GGUF"]) self.assertEqual(tasks[("minimax_h3", "MiniMax-H3-Q4-GGUF")], "tts") def test_directory_style_entry_matches_a_file_style_selection(self): self._tmp = tempfile.TemporaryDirectory() self.addCleanup(self._tmp.cleanup) server_json = Path(self._tmp.name) / "server.json" server_json.write_text(json.dumps({"models": [{ "id": "MiniMax-H3-Q4-GGUF", "family": "minimax_h3", "path": "models/MiniMax-H3-Q4-GGUF", }]}), encoding="utf-8") unused = make_server.models.unused_installed_entries( server_json, {"models/MiniMax-H3-Q4-GGUF/dit.gguf"}) self.assertEqual(unused, []) class WizardCompanionGuidanceTests(unittest.TestCase): """_build_entries returns MioCodec guidance for MioTTS selections.""" _CATALOG_ENTRY = { "family": "miotts", "display_name": "MioTTS", "description": "", "languages": ["en"], "tasks": ["tts", "clone"], "clone_capable": True, "packages": [{ "id": "miotts_1_7b_q8_0", "default": True, "format": "gguf", "target_directory": "MioTTS-1.7B-GGUF", "strip_prefix": "MioTTS-1.7B-GGUF", "files": ["MioTTS-1.7B-GGUF/miotts-1.7b-q8_0.gguf"], }], "install_id": "miotts_1_7b_q8_0", "default_path": "models/MioTTS-1.7B-GGUF", } def test_miotts_selection_carries_the_mio_codec_companion(self): entries, _ids, guidance, companions, _design, _clone = \ make_server.wizard._build_entries( ["miotts"], {"miotts": [{"target_directory": "MioTTS-1.7B-GGUF", "install_id": "miotts_1_7b_q8_0", "design": False, "recommended": True}]}, {"miotts": self._CATALOG_ENTRY}, lambda install_id: "tts") self.assertEqual(companions, [("MioCodec 25Hz 44.1kHz v2 (required by MioTTS)", "miocodec_q8_0")]) self.assertEqual(entries[0]["path"], "models/MioTTS-1.7B-GGUF") self.assertEqual(len(guidance), 1) def test_other_families_carry_no_companions(self): catalog_entry = dict(self._CATALOG_ENTRY, family="voxcpm2", display_name="VoxCPM2") _entries, _ids, _guidance, companions, _design, _clone = \ make_server.wizard._build_entries( ["voxcpm2"], {"voxcpm2": [{"target_directory": "VoxCPM2-GGUF", "install_id": "voxcpm2_q8_0", "design": False, "recommended": True}]}, {"voxcpm2": catalog_entry}, lambda install_id: "tts") self.assertEqual(companions, [])