import contextlib import io import importlib import os import sys import tempfile import unittest from pathlib import Path from types import SimpleNamespace from unittest import mock REQUIRED_MODEL_FILES = [ "config.yaml", "bpe.model", "gpt.pth", "s2mel.pth", "wav2vec2bert_stats.pt", "feat1.pt", "feat2.pt", ] REQUIRED_MODEL_DIRS = [ "qwen0.6bemo4-merge", ] AUX_MODEL_FILES = [ "hf_cache/semantic_codec_model.safetensors", "hf_cache/campplus_cn_common.bin", "hf_cache/bigvgan/config.json", "hf_cache/bigvgan/bigvgan_generator.pt", ] AUX_MODEL_DIRS = [ "hf_cache/w2v-bert-2.0", ] def make_model_dir(path): path.mkdir(parents=True) for filename in REQUIRED_MODEL_FILES: (path / filename).write_text("placeholder", encoding="utf-8") for dirname in REQUIRED_MODEL_DIRS: (path / dirname).mkdir() for filename in AUX_MODEL_FILES: target = path / filename target.parent.mkdir(parents=True, exist_ok=True) target.write_text("placeholder", encoding="utf-8") for dirname in AUX_MODEL_DIRS: target = path / dirname target.mkdir(parents=True, exist_ok=True) (target / "config.json").write_text("placeholder", encoding="utf-8") def fake_torch(): return SimpleNamespace( cuda=SimpleNamespace(is_available=lambda: False, device_count=lambda: 0), xpu=SimpleNamespace(is_available=lambda: False, device_count=lambda: 0), backends=SimpleNamespace(mps=SimpleNamespace(is_available=lambda: False)), ) def patched_imports(): real_import_module = importlib.import_module def import_module(name, package=None): if name == "torch": return fake_torch() if name in {"torchaudio", "indextts"}: return SimpleNamespace(__name__=name) return real_import_module(name, package) return mock.patch("importlib.import_module", side_effect=import_module) def user_state_paths(temp_path): if sys.platform == "win32": return { "env": { "APPDATA": str(temp_path / "roaming"), "LOCALAPPDATA": str(temp_path / "local"), }, "config_path": temp_path / "roaming" / "IndexTTS" / "config.toml", "model_dir": temp_path / "local" / "IndexTTS" / "models" / "IndexTTS-2", } if sys.platform == "darwin": app_support = temp_path / "Library" / "Application Support" / "IndexTTS" return { "env": {"HOME": str(temp_path)}, "config_path": app_support / "config.toml", "model_dir": app_support / "models" / "IndexTTS-2", } return { "env": { "XDG_CONFIG_HOME": str(temp_path / "config"), "XDG_DATA_HOME": str(temp_path / "data"), }, "config_path": temp_path / "config" / "indextts" / "config.toml", "model_dir": temp_path / "data" / "indextts" / "models" / "IndexTTS-2", } class ConfigCommandTests(unittest.TestCase): def run_cli(self, args, **kwargs): from indextts.cli_v2 import main stdout = io.StringIO() stderr = io.StringIO() with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr): exit_code = main(args, **kwargs) return exit_code, stdout.getvalue(), stderr.getvalue() def test_init_creates_persistent_config_and_default_model_directory_without_model_files(self): with tempfile.TemporaryDirectory() as temp_dir: state = user_state_paths(Path(temp_dir)) with mock.patch.dict(os.environ, state["env"], clear=False): exit_code, stdout, stderr = self.run_cli(["init"]) config_text = state["config_path"].read_text(encoding="utf-8") model_dir_files = list(state["model_dir"].iterdir()) self.assertEqual(exit_code, 0) self.assertIn(f"Config: {state['config_path']}", stdout) self.assertIn(f"Model directory: {state['model_dir']}", stdout) self.assertEqual(stderr, "") self.assertIn(f'model_dir = "{state["model_dir"].as_posix()}"', config_text) self.assertEqual(model_dir_files, []) def test_init_with_model_dir_persists_the_requested_model_directory(self): with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) state = user_state_paths(temp_path) model_dir = temp_path / "custom-models" with mock.patch.dict(os.environ, state["env"], clear=False): exit_code, stdout, stderr = self.run_cli(["init", "--model-dir", str(model_dir)]) config_text = state["config_path"].read_text(encoding="utf-8") model_dir_exists = model_dir.is_dir() self.assertEqual(exit_code, 0) self.assertIn(f"Model directory: {model_dir}", stdout) self.assertEqual(stderr, "") self.assertTrue(model_dir_exists) self.assertIn(f'model_dir = "{model_dir.as_posix()}"', config_text) def test_config_path_prints_the_persistent_config_file_location(self): with tempfile.TemporaryDirectory() as temp_dir: state = user_state_paths(Path(temp_dir)) with mock.patch.dict(os.environ, state["env"], clear=False): exit_code, stdout, stderr = self.run_cli(["config", "path"]) self.assertEqual(exit_code, 0) self.assertEqual(stdout, f"{state['config_path']}\n") self.assertEqual(stderr, "") def test_config_set_model_dir_persists_the_model_resource_directory(self): with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) state = user_state_paths(temp_path) model_dir = temp_path / "persisted-models" with mock.patch.dict(os.environ, state["env"], clear=False): exit_code, stdout, stderr = self.run_cli(["config", "set", "model_dir", str(model_dir)]) config_text = state["config_path"].read_text(encoding="utf-8") self.assertEqual(exit_code, 0) self.assertEqual(stdout, f"model_dir = {model_dir}\n") self.assertEqual(stderr, "") self.assertIn(f'model_dir = "{model_dir.as_posix()}"', config_text) def test_config_set_runtime_preferences_persists_device_and_boolean_values(self): with tempfile.TemporaryDirectory() as temp_dir: state = user_state_paths(Path(temp_dir)) with mock.patch.dict(os.environ, state["env"], clear=False): first = self.run_cli(["config", "set", "default_device", "cuda:0"]) second = self.run_cli(["config", "set", "use_fp16", "true"]) third = self.run_cli(["config", "set", "use_deepspeed", "false"]) fourth = self.run_cli(["config", "set", "use_cuda_kernel", "true"]) config_text = state["config_path"].read_text(encoding="utf-8") self.assertEqual(first, (0, "default_device = cuda:0\n", "")) self.assertEqual(second, (0, "use_fp16 = true\n", "")) self.assertEqual(third, (0, "use_deepspeed = false\n", "")) self.assertEqual(fourth, (0, "use_cuda_kernel = true\n", "")) self.assertIn('default_device = "cuda:0"', config_text) self.assertIn("use_fp16 = true", config_text) self.assertIn("use_deepspeed = false", config_text) self.assertIn("use_cuda_kernel = true", config_text) def test_config_set_boolean_preference_rejects_non_boolean_values(self): with tempfile.TemporaryDirectory() as temp_dir: state = user_state_paths(Path(temp_dir)) with mock.patch.dict(os.environ, state["env"], clear=False): exit_code, stdout, stderr = self.run_cli(["config", "set", "use_fp16", "yes"]) config_exists = state["config_path"].exists() self.assertEqual(exit_code, 1) self.assertEqual(stdout, "") self.assertIn("ERROR: use_fp16 must be true or false", stderr) self.assertFalse(config_exists) def test_config_get_prints_the_current_persistent_config(self): with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) state = user_state_paths(temp_path) model_dir = temp_path / "models" with mock.patch.dict(os.environ, state["env"], clear=False): self.run_cli(["config", "set", "model_dir", str(model_dir)]) self.run_cli(["config", "set", "default_device", "cpu"]) exit_code, stdout, stderr = self.run_cli(["config", "get"]) self.assertEqual(exit_code, 0) self.assertIn(f'model_dir = "{model_dir.as_posix()}"', stdout) self.assertIn('default_device = "cpu"', stdout) self.assertEqual(stderr, "") def test_check_uses_persisted_model_dir_when_command_line_and_environment_do_not_override_it(self): with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) state = user_state_paths(temp_path) model_dir = temp_path / "persisted-models" make_model_dir(model_dir) with mock.patch.dict(os.environ, state["env"], clear=False): self.run_cli(["config", "set", "model_dir", str(model_dir)]) with patched_imports(): exit_code, stdout, stderr = self.run_cli(["check"]) self.assertEqual(exit_code, 0) self.assertIn(f"OK: model directory {model_dir}", stdout) self.assertEqual(stderr, "") def test_check_model_dir_resolution_prioritizes_command_line_then_environment_then_config(self): with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) state = user_state_paths(temp_path) cli_model_dir = temp_path / "cli-models" env_model_dir = temp_path / "env-models" persisted_model_dir = temp_path / "persisted-models" for model_dir in [cli_model_dir, env_model_dir, persisted_model_dir]: make_model_dir(model_dir) with mock.patch.dict(os.environ, state["env"], clear=False): self.run_cli(["config", "set", "model_dir", str(persisted_model_dir)]) with patched_imports(): cli_result = self.run_cli( ["check", "--model-dir", str(cli_model_dir)], ) with mock.patch.dict(os.environ, {"INDEXTTS2_MODEL_DIR": str(env_model_dir)}, clear=False): with patched_imports(): env_result = self.run_cli(["check"]) with patched_imports(): config_result = self.run_cli(["check"]) self.assertIn(f"OK: model directory {cli_model_dir}", cli_result[1]) self.assertIn(f"OK: model directory {env_model_dir}", env_result[1]) self.assertIn(f"OK: model directory {persisted_model_dir}", config_result[1]) def test_check_initializes_default_state_and_checks_the_platform_default_model_dir(self): with tempfile.TemporaryDirectory() as temp_dir: state = user_state_paths(Path(temp_dir)) with mock.patch.dict(os.environ, state["env"], clear=False): with mock.patch.dict(os.environ, {"INDEXTTS2_MODEL_DIR": ""}, clear=False): exit_code, stdout, stderr = self.run_cli(["check"]) config_text = state["config_path"].read_text(encoding="utf-8") model_dir_exists = state["model_dir"].is_dir() self.assertEqual(exit_code, 2) self.assertEqual(stdout, "") self.assertIn("ERROR: missing required model files", stderr) self.assertIn(state["model_dir"].as_posix(), config_text) self.assertTrue(model_dir_exists) def test_check_with_command_model_dir_still_initializes_default_state_without_persisting_override(self): with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) state = user_state_paths(temp_path) cli_model_dir = temp_path / "cli-models" make_model_dir(cli_model_dir) with mock.patch.dict(os.environ, state["env"], clear=False): with patched_imports(): exit_code, stdout, stderr = self.run_cli(["check", "--model-dir", str(cli_model_dir)]) config_text = state["config_path"].read_text(encoding="utf-8") self.assertEqual(exit_code, 0) self.assertIn(f"OK: model directory {cli_model_dir}", stdout) self.assertEqual(stderr, "") self.assertIn(f'model_dir = "{state["model_dir"].as_posix()}"', config_text) self.assertNotIn(cli_model_dir.as_posix(), config_text) def test_synth_uses_persisted_model_dir_and_runtime_preferences_by_default(self): with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) state = user_state_paths(temp_path) model_dir = temp_path / "models" voice_path = temp_path / "voice.wav" output_path = temp_path / "out.wav" calls = [] make_model_dir(model_dir) voice_path.write_bytes(b"voice") class FakeIndexTTS2: def __init__(self, **kwargs): calls.append(("init", kwargs)) def infer(self, **kwargs): calls.append(("infer", kwargs)) with mock.patch.dict(os.environ, state["env"], clear=False): self.run_cli(["config", "set", "model_dir", str(model_dir)]) self.run_cli(["config", "set", "default_device", "cpu"]) self.run_cli(["config", "set", "use_fp16", "true"]) self.run_cli(["config", "set", "use_deepspeed", "true"]) self.run_cli(["config", "set", "use_cuda_kernel", "true"]) exit_code, stdout, stderr = self.run_cli( [ "synth", "--text", "hello", "--voice", str(voice_path), "--output", str(output_path), ], tts_factory=FakeIndexTTS2, ) self.assertEqual(exit_code, 0) self.assertEqual(stdout, f"Generated: {output_path}\n") self.assertEqual(stderr, "") self.assertEqual( calls[0][1], { "cfg_path": str(model_dir / "config.yaml"), "model_dir": str(model_dir), "use_fp16": True, "device": "cpu", "use_cuda_kernel": True, "use_deepspeed": True, }, ) def test_batch_uses_persisted_model_dir_and_runtime_preferences_by_default(self): with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) state = user_state_paths(temp_path) model_dir = temp_path / "models" voice_path = temp_path / "voice.wav" batch_file = temp_path / "batch.jsonl" output_path = temp_path / "out.wav" calls = [] make_model_dir(model_dir) voice_path.write_bytes(b"voice") batch_file.write_text('{"text": "hello", "voice": "voice.wav", "output": "out.wav"}\n', encoding="utf-8") class FakeIndexTTS2: def __init__(self, **kwargs): calls.append(("init", kwargs)) def infer(self, **kwargs): calls.append(("infer", kwargs)) Path(kwargs["output_path"]).write_bytes(b"audio") with mock.patch.dict(os.environ, state["env"], clear=False): self.run_cli(["config", "set", "model_dir", str(model_dir)]) self.run_cli(["config", "set", "default_device", "cpu"]) self.run_cli(["config", "set", "use_fp16", "true"]) self.run_cli(["config", "set", "use_deepspeed", "true"]) self.run_cli(["config", "set", "use_cuda_kernel", "true"]) exit_code, stdout, stderr = self.run_cli( ["batch", "--batch-file", str(batch_file)], tts_factory=FakeIndexTTS2, ) self.assertEqual(exit_code, 0) self.assertEqual(stdout, f"Generated: {output_path}\nBatch complete: 1 tasks generated\n") self.assertEqual(stderr, "") self.assertEqual( calls[0][1], { "cfg_path": str(model_dir / "config.yaml"), "model_dir": str(model_dir), "use_fp16": True, "device": "cpu", "use_cuda_kernel": True, "use_deepspeed": True, }, ) def test_batch_command_line_can_disable_persisted_boolean_runtime_preferences_for_one_run(self): with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) state = user_state_paths(temp_path) model_dir = temp_path / "models" voice_path = temp_path / "voice.wav" batch_file = temp_path / "batch.jsonl" calls = [] make_model_dir(model_dir) voice_path.write_bytes(b"voice") batch_file.write_text('{"text": "hello", "voice": "voice.wav", "output": "out.wav"}\n', encoding="utf-8") class FakeIndexTTS2: def __init__(self, **kwargs): calls.append(("init", kwargs)) def infer(self, **kwargs): calls.append(("infer", kwargs)) Path(kwargs["output_path"]).write_bytes(b"audio") with mock.patch.dict(os.environ, state["env"], clear=False): self.run_cli(["config", "set", "model_dir", str(model_dir)]) self.run_cli(["config", "set", "use_fp16", "true"]) self.run_cli(["config", "set", "use_deepspeed", "true"]) self.run_cli(["config", "set", "use_cuda_kernel", "true"]) before_config = state["config_path"].read_text(encoding="utf-8") exit_code, stdout, stderr = self.run_cli( [ "batch", "--batch-file", str(batch_file), "--no-fp16", "--no-deepspeed", "--no-cuda-kernel", ], tts_factory=FakeIndexTTS2, ) after_config = state["config_path"].read_text(encoding="utf-8") self.assertEqual(exit_code, 0) self.assertIn("Batch complete: 1 tasks generated", stdout) self.assertEqual(stderr, "") self.assertEqual(before_config, after_config) self.assertEqual(calls[0][1]["use_fp16"], False) self.assertEqual(calls[0][1]["use_deepspeed"], False) self.assertEqual(calls[0][1]["use_cuda_kernel"], False) def test_synth_command_line_overrides_do_not_rewrite_persistent_config(self): with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) state = user_state_paths(temp_path) persisted_model_dir = temp_path / "persisted-models" cli_model_dir = temp_path / "cli-models" voice_path = temp_path / "voice.wav" output_path = temp_path / "out.wav" calls = [] make_model_dir(persisted_model_dir) make_model_dir(cli_model_dir) voice_path.write_bytes(b"voice") class FakeIndexTTS2: def __init__(self, **kwargs): calls.append(("init", kwargs)) def infer(self, **kwargs): calls.append(("infer", kwargs)) with mock.patch.dict(os.environ, state["env"], clear=False): self.run_cli(["config", "set", "model_dir", str(persisted_model_dir)]) self.run_cli(["config", "set", "default_device", "cpu"]) self.run_cli(["config", "set", "use_fp16", "false"]) before_config = state["config_path"].read_text(encoding="utf-8") exit_code, stdout, stderr = self.run_cli( [ "synth", "--text", "hello", "--voice", str(voice_path), "--output", str(output_path), "--model-dir", str(cli_model_dir), "--device", "cuda:0", "--fp16", "--deepspeed", "--cuda-kernel", ], tts_factory=FakeIndexTTS2, ) after_config = state["config_path"].read_text(encoding="utf-8") self.assertEqual(exit_code, 0) self.assertEqual(stdout, f"Generated: {output_path}\n") self.assertEqual(stderr, "") self.assertEqual(before_config, after_config) self.assertEqual(calls[0][1]["model_dir"], str(cli_model_dir)) self.assertEqual(calls[0][1]["device"], "cuda:0") self.assertEqual(calls[0][1]["use_fp16"], True) self.assertEqual(calls[0][1]["use_deepspeed"], True) self.assertEqual(calls[0][1]["use_cuda_kernel"], True) def test_synth_command_line_can_disable_persisted_boolean_runtime_preferences_for_one_run(self): with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) state = user_state_paths(temp_path) model_dir = temp_path / "models" voice_path = temp_path / "voice.wav" output_path = temp_path / "out.wav" calls = [] make_model_dir(model_dir) voice_path.write_bytes(b"voice") class FakeIndexTTS2: def __init__(self, **kwargs): calls.append(("init", kwargs)) def infer(self, **kwargs): calls.append(("infer", kwargs)) with mock.patch.dict(os.environ, state["env"], clear=False): self.run_cli(["config", "set", "model_dir", str(model_dir)]) self.run_cli(["config", "set", "use_fp16", "true"]) self.run_cli(["config", "set", "use_deepspeed", "true"]) self.run_cli(["config", "set", "use_cuda_kernel", "true"]) before_config = state["config_path"].read_text(encoding="utf-8") exit_code, stdout, stderr = self.run_cli( [ "synth", "--text", "hello", "--voice", str(voice_path), "--output", str(output_path), "--no-fp16", "--no-deepspeed", "--no-cuda-kernel", ], tts_factory=FakeIndexTTS2, ) after_config = state["config_path"].read_text(encoding="utf-8") self.assertEqual(exit_code, 0) self.assertEqual(stdout, f"Generated: {output_path}\n") self.assertEqual(stderr, "") self.assertEqual(before_config, after_config) self.assertEqual(calls[0][1]["use_fp16"], False) self.assertEqual(calls[0][1]["use_deepspeed"], False) self.assertEqual(calls[0][1]["use_cuda_kernel"], False) if __name__ == "__main__": unittest.main()