aboutsummaryrefslogtreecommitdiff
path: root/app/tests/test_backends_envs.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-27 00:04:16 -0400
committerhistoria <historiavg@proton.me>2026-08-27 00:04:16 -0400
commit5b98993b13dafe9a85495e4c68ad9b42863ef2bf (patch)
tree57db1566deb5351ec6bfb8c70de4126bbc5fc2da /app/tests/test_backends_envs.py
parentf18f421d9180ae0e3bff9496b1fdaf53d3624a75 (diff)
downloadtts-audiobook-generator-5b98993b13dafe9a85495e4c68ad9b42863ef2bf.tar.gz
feat: separate venvs for qwen-tts and faster, manage (un)installs
Diffstat (limited to 'app/tests/test_backends_envs.py')
-rw-r--r--app/tests/test_backends_envs.py84
1 files changed, 83 insertions, 1 deletions
diff --git a/app/tests/test_backends_envs.py b/app/tests/test_backends_envs.py
index 184a3b3..8b17458 100644
--- a/app/tests/test_backends_envs.py
+++ b/app/tests/test_backends_envs.py
@@ -16,25 +16,47 @@ class EnvPathTests(unittest.TestCase):
self.assertEqual(envs.ENV_DIR.name, "tts")
self.assertEqual(envs.ENV_DIR.parent.name, "envs")
+ def test_backend_env_dirs_are_separate_from_the_app_env(self):
+ # Each pip-installed backend gets its own venv next to the app's:
+ # qwen-tts and faster-qwen3-tts both ship a qwen_tts module with
+ # conflicting transformers pins, so they must never share one.
+ self.assertEqual(envs.QWEN_ENV_DIR.name, "qwen")
+ self.assertEqual(envs.FASTER_ENV_DIR.name, "faster")
+ self.assertEqual(envs.QWEN_ENV_DIR.parent, envs.ENV_DIR.parent)
+ distinct = {envs.ENV_DIR, envs.QWEN_ENV_DIR, envs.FASTER_ENV_DIR}
+ self.assertEqual(len(distinct), 3)
+
def test_env_python_posix(self):
with patch.object(envs, "_is_windows", return_value=False):
self.assertEqual(envs.env_python(),
envs.ENV_DIR / "bin" / "python")
+ self.assertEqual(
+ envs.env_python(envs.QWEN_ENV_DIR),
+ envs.QWEN_ENV_DIR / "bin" / "python")
def test_env_python_windows(self):
with patch.object(envs, "_is_windows", return_value=True):
self.assertEqual(envs.env_python(),
envs.ENV_DIR / "Scripts" / "python.exe")
+ self.assertEqual(
+ envs.env_python(envs.FASTER_ENV_DIR),
+ envs.FASTER_ENV_DIR / "Scripts" / "python.exe")
def test_env_script_posix(self):
with patch.object(envs, "_is_windows", return_value=False):
self.assertEqual(envs.env_script("qwen-tts-demo"),
envs.ENV_DIR / "bin" / "qwen-tts-demo")
+ self.assertEqual(
+ envs.env_script("qwen-tts-demo", envs.QWEN_ENV_DIR),
+ envs.QWEN_ENV_DIR / "bin" / "qwen-tts-demo")
def test_env_script_windows(self):
with patch.object(envs, "_is_windows", return_value=True):
self.assertEqual(envs.env_script("qwen-tts-demo"),
envs.ENV_DIR / "Scripts" / "qwen-tts-demo.exe")
+ self.assertEqual(
+ envs.env_script("qwen-tts-demo", envs.QWEN_ENV_DIR),
+ envs.QWEN_ENV_DIR / "Scripts" / "qwen-tts-demo.exe")
def test_env_exists_false_when_python_missing(self):
with patch.object(envs, "env_python",
@@ -63,6 +85,13 @@ class CreateEnvTests(unittest.TestCase):
self.assertEqual(argv[2], "venv")
self.assertEqual(argv[3], str(envs.ENV_DIR))
+ def test_create_env_targets_the_requested_env_dir(self):
+ with patch.object(envs.common, "run_console_subprocess",
+ return_value=0) as run:
+ envs.create_env(envs.FASTER_ENV_DIR)
+ argv = run.call_args[0][0]
+ self.assertEqual(argv[3], str(envs.FASTER_ENV_DIR))
+
def test_create_env_reports_remediation_on_failure(self):
with patch.object(envs.common, "run_console_subprocess",
return_value=1):
@@ -84,12 +113,31 @@ class PipInstallTests(unittest.TestCase):
side_effect=fake_run):
rc = envs.pip_install(["qwen-tts"])
self.assertEqual(rc, 0)
- mk.assert_called_once_with()
+ mk.assert_called_once_with(None)
# The actual pip call targets the venv's python.
self.assertEqual(calls[0][0], str(envs.env_python()))
self.assertIn("pip", calls[0])
self.assertIn("qwen-tts", calls[0])
+ def test_backend_env_targets_the_backend_python_and_env(self):
+ calls = []
+
+ def fake_run(argv, **kwargs):
+ calls.append(list(argv))
+ return 0
+
+ with patch.object(envs, "env_exists", return_value=False), \
+ patch.object(envs, "create_env", return_value=0) as mk, \
+ patch.object(envs.common, "run_console_subprocess",
+ side_effect=fake_run):
+ rc = envs.pip_install(["qwen-tts"], env_dir=envs.QWEN_ENV_DIR)
+ self.assertEqual(rc, 0)
+ # Both create-if-missing and pip itself are scoped to the qwen env;
+ # the app env is never touched.
+ mk.assert_called_once_with(envs.QWEN_ENV_DIR)
+ self.assertEqual(calls[0][0],
+ str(envs.env_python(envs.QWEN_ENV_DIR)))
+
def test_skips_create_when_env_exists(self):
with patch.object(envs, "env_exists", return_value=True), \
patch.object(envs, "create_env") as mk, \
@@ -115,6 +163,13 @@ class PipUninstallTests(unittest.TestCase):
self.assertEqual(rc, 0)
run.assert_not_called()
+ def test_missing_backend_env_is_also_a_noop(self):
+ with patch.object(envs, "env_exists", return_value=False), \
+ patch.object(envs.common, "run_console_subprocess") as run:
+ rc = envs.pip_uninstall(["qwen-tts"], env_dir=envs.QWEN_ENV_DIR)
+ self.assertEqual(rc, 0)
+ run.assert_not_called()
+
def test_runs_pip_uninstall_against_the_venv_python(self):
calls = []
@@ -133,6 +188,21 @@ class PipUninstallTests(unittest.TestCase):
self.assertIn("-y", calls[0])
self.assertIn("qwen-tts", calls[0])
+ def test_targets_the_requested_env_python(self):
+ calls = []
+
+ def fake_run(argv, **kwargs):
+ calls.append(list(argv))
+ return 0
+
+ with patch.object(envs, "env_exists", return_value=True), \
+ patch.object(envs.common, "run_console_subprocess",
+ side_effect=fake_run):
+ envs.pip_uninstall(["faster-qwen3-tts"],
+ env_dir=envs.FASTER_ENV_DIR)
+ self.assertEqual(calls[0][0],
+ str(envs.env_python(envs.FASTER_ENV_DIR)))
+
def test_streams_to_emit_when_given(self):
with patch.object(envs, "env_exists", return_value=True), \
patch.object(envs.common, "run_console_subprocess",
@@ -167,6 +237,18 @@ class ModuleAvailableTests(unittest.TestCase):
self.assertEqual(argv[0], str(envs.env_python()))
self.assertIn("import qwen_tts", argv[2])
+ def test_probes_the_requested_env_interpreter(self):
+ import subprocess
+ fake = subprocess.CompletedProcess(args=["x"], returncode=0)
+ with patch.object(envs, "env_exists", return_value=True), \
+ patch("subprocess.run", return_value=fake) as run:
+ self.assertTrue(
+ envs.module_available("qwen_tts", envs.QWEN_ENV_DIR))
+ argv = run.call_args[0][0]
+ self.assertEqual(argv[0],
+ str(envs.env_python(envs.QWEN_ENV_DIR)))
+ self.assertIn("import qwen_tts", argv[2])
+
def test_false_when_subprocess_exits_nonzero(self):
import subprocess
fake = subprocess.CompletedProcess(args=["x"], returncode=1)