diff options
Diffstat (limited to 'app/tests')
| -rw-r--r-- | app/tests/test_backends.py | 37 | ||||
| -rw-r--r-- | app/tests/test_backends_audiocpp.py | 29 | ||||
| -rw-r--r-- | app/tests/test_backends_envs.py | 45 | ||||
| -rw-r--r-- | app/tests/test_backends_faster.py | 58 | ||||
| -rw-r--r-- | app/tests/test_hub.py | 79 |
5 files changed, 244 insertions, 4 deletions
diff --git a/app/tests/test_backends.py b/app/tests/test_backends.py index 0d3be37..8ea6a3f 100644 --- a/app/tests/test_backends.py +++ b/app/tests/test_backends.py @@ -325,3 +325,40 @@ class QwenSetupScreenTests(unittest.TestCase): self.assertIs(mk_steps.call_args[0][0], settings) mk_run.assert_called_once() self.assertEqual(mk_run.call_args[0][2], steps) + + +class QwenUninstallTests(unittest.TestCase): + """qwen.uninstall: stop both servers, then pip-uninstall the package.""" + + def test_stops_servers_and_pips(self): + from backends import qwen + with patch.object(qwen.servers, "stop") as mk_stop, \ + patch.object(qwen.common, "pip_uninstall", + return_value=0) as mk_pip: + rc = qwen.uninstall(emit="EMIT") + self.assertEqual(rc, 0) + self.assertEqual([c.args[0] for c in mk_stop.call_args_list], + ["qwen-custom", "qwen-clone"]) + # The task view's emit is forwarded so pip never touches the terminal. + mk_pip.assert_called_once_with([qwen.QWEN_PIP_PKG], emit="EMIT") + + def test_cancel_before_pip_skips_uninstall(self): + import threading + + from backends import qwen + cancel = threading.Event() + cancel.set() + with patch.object(qwen.servers, "stop") as mk_stop, \ + patch.object(qwen.common, "pip_uninstall") as mk_pip: + rc = qwen.uninstall(cancel=cancel) + self.assertEqual(rc, 130) + self.assertEqual(mk_stop.call_count, 2) + mk_pip.assert_not_called() + + def test_pip_failure_warns_but_still_succeeds(self): + from backends import qwen + with patch.object(qwen.servers, "stop"), \ + patch.object(qwen.common, "pip_uninstall", + return_value=1): + rc = qwen.uninstall() + self.assertEqual(rc, 0) diff --git a/app/tests/test_backends_audiocpp.py b/app/tests/test_backends_audiocpp.py index e206ddd..665dbf0 100644 --- a/app/tests/test_backends_audiocpp.py +++ b/app/tests/test_backends_audiocpp.py @@ -1836,6 +1836,35 @@ class UninstallTests(unittest.TestCase): self.assertEqual(rc, 0) mk_stop.assert_called_once_with("audiocpp") + 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, "find_local_checkout", + return_value=checkout), \ + patch.object(make_server.servers, "stop"): + rc = make_server.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, "find_local_checkout", + return_value=checkout), \ + patch.object(make_server.servers, "stop"): + rc = make_server.uninstall(cancel=cancel) + self.assertEqual(rc, 130) + self.assertTrue(checkout.exists()) + if __name__ == "__main__": unittest.main() diff --git a/app/tests/test_backends_envs.py b/app/tests/test_backends_envs.py index bf27f60..82d903a 100644 --- a/app/tests/test_backends_envs.py +++ b/app/tests/test_backends_envs.py @@ -106,6 +106,51 @@ class PipInstallTests(unittest.TestCase): run.assert_not_called() +class PipUninstallTests(unittest.TestCase): + def test_missing_env_is_success_without_running_pip(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"]) + self.assertEqual(rc, 0) + run.assert_not_called() + + def test_runs_pip_uninstall_against_the_venv_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): + rc = envs.pip_uninstall(["qwen-tts"]) + self.assertEqual(rc, 0) + # The uninstall targets the venv's python. + self.assertEqual(calls[0][0], str(envs.env_python())) + self.assertIn("uninstall", calls[0]) + self.assertIn("-y", calls[0]) + self.assertIn("qwen-tts", calls[0]) + + def test_streams_to_emit_when_given(self): + with patch.object(envs, "env_exists", return_value=True), \ + patch.object(envs.common, "run_console_subprocess", + return_value=0) as run: + rc = envs.pip_uninstall(["qwen-tts"], emit="EMIT") + self.assertEqual(rc, 0) + # The task view's emit is forwarded so pip never touches the + # terminal behind curses. + self.assertEqual(run.call_args.kwargs.get("emit"), "EMIT") + + def test_console_path_passes_no_emit(self): + with patch.object(envs, "env_exists", return_value=True), \ + patch.object(envs.common, "run_console_subprocess", + return_value=0) as run: + rc = envs.pip_uninstall(["qwen-tts"]) + self.assertEqual(rc, 0) + self.assertIsNone(run.call_args.kwargs.get("emit")) + + class ModuleAvailableTests(unittest.TestCase): def test_false_when_env_missing(self): with patch.object(envs, "env_exists", return_value=False): diff --git a/app/tests/test_backends_faster.py b/app/tests/test_backends_faster.py index eb1fa28..7e27b47 100644 --- a/app/tests/test_backends_faster.py +++ b/app/tests/test_backends_faster.py @@ -3,6 +3,7 @@ import json import sys import tempfile +import threading import unittest import contextlib from pathlib import Path @@ -276,3 +277,60 @@ class SetupScreenTests(unittest.TestCase): self.assertIs(mk_steps.call_args[0][0], settings) mk_run.assert_called_once() self.assertEqual(mk_run.call_args[0][2], steps) + + +class UninstallTests(unittest.TestCase): + """uninstall: stop the server, pip-uninstall, delete the checkout.""" + + def test_pips_and_removes_checkout(self): + with tempfile.TemporaryDirectory() as td: + checkout = Path(td) / "faster-qwen3-tts" + checkout.mkdir() + with patch.object(make_voices, "_checkout", + return_value=checkout), \ + patch.object(make_voices.servers, "stop") as mk_stop, \ + patch.object(make_voices.common, "pip_uninstall", + return_value=0) as mk_pip: + rc = make_voices.uninstall(emit="EMIT") + self.assertEqual(rc, 0) + mk_stop.assert_called_once_with("faster") + # The task view's emit is forwarded so pip never touches the terminal. + mk_pip.assert_called_once_with(["faster-qwen3-tts"], emit="EMIT") + self.assertFalse(checkout.exists()) + + def test_no_checkout_still_uninstalls_the_package(self): + with patch.object(make_voices, "_checkout", + return_value=Path("/no/such/dir")), \ + patch.object(make_voices.servers, "stop"), \ + patch.object(make_voices.common, "pip_uninstall", + return_value=0) as mk_pip: + rc = make_voices.uninstall() + self.assertEqual(rc, 0) + mk_pip.assert_called_once_with(["faster-qwen3-tts"], emit=None) + + def test_cancel_before_pip_skips_everything_after_stopping(self): + cancel = threading.Event() + cancel.set() + with patch.object(make_voices.servers, "stop") as mk_stop, \ + patch.object(make_voices.common, "pip_uninstall") as mk_pip: + rc = make_voices.uninstall(cancel=cancel) + self.assertEqual(rc, 130) + mk_stop.assert_called_once_with("faster") + mk_pip.assert_not_called() + + def test_cancel_before_delete_keeps_checkout(self): + # Cancel between phases: pip runs to completion, but a pending + # cancellation stops the checkout deletion from starting. + with tempfile.TemporaryDirectory() as td: + checkout = Path(td) / "faster-qwen3-tts" + checkout.mkdir() + cancel = threading.Event() + cancel.set() + with patch.object(make_voices, "_checkout", + return_value=checkout), \ + patch.object(make_voices.servers, "stop"), \ + patch.object(make_voices.common, "pip_uninstall", + return_value=0): + rc = make_voices.uninstall(cancel=cancel) + self.assertEqual(rc, 130) + self.assertTrue(checkout.exists()) diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py index 965cbaa..ec079f5 100644 --- a/app/tests/test_hub.py +++ b/app/tests/test_hub.py @@ -1730,14 +1730,85 @@ class ConfigureBackendsDispatchTests(unittest.TestCase): self.assertIs(result, tui.Wizard.BACK) self.assertEqual(flashes, ["kaboom"]) - def test_screen_uninstall_runs_uninstall_and_goes_back(self): - import contextlib + def test_screen_uninstall_runs_in_task_view_and_goes_back(self): info = BackendInfo("qwen", "qwen-tts", lambda: None, lambda: 0) with patch.object(hub._Hub, "_pick_backend", return_value=info), \ + patch.object(hub.tui, "confirm", return_value=True), \ + patch.object(hub.taskview, "run_steps", + return_value=0) as mk_run, \ patch.object(info, "uninstall") as mk_uninstall, \ - patch.object(hub.tui, "suspend", contextlib.nullcontext): + patch.object(hub.tui, "flash"): result = hub._Hub(None).screen_uninstall() - mk_uninstall.assert_called_once_with() + # The uninstall runs as one task-view step on the session (no + # suspend); executing the step forwards emit/cancel to uninstall. + mk_run.assert_called_once() + self.assertEqual(mk_run.call_args[0][0], None) + steps = mk_run.call_args[0][2] + self.assertEqual([step.title for step in steps], + ["Uninstall qwen-tts"]) + self.assertFalse(mk_run.call_args.kwargs["wait_on_finish"]) + emit = lambda line: None + steps[0].work(emit, None) + mk_uninstall.assert_called_once_with(emit=emit, cancel=None) + self.assertIs(result, tui.Wizard.BACK) + + def test_screen_uninstall_success_flashes_ok(self): + flashes = [] + + def fake_flash(scr, text, kind="warn"): + flashes.append((text, kind)) + + info = BackendInfo("qwen", "qwen-tts", lambda: None, lambda: 0) + with patch.object(hub._Hub, "_pick_backend", return_value=info), \ + patch.object(hub.tui, "confirm", return_value=True), \ + patch.object(hub.taskview, "run_steps", return_value=0), \ + patch.object(info, "uninstall"), \ + patch.object(hub.tui, "flash", fake_flash): + result = hub._Hub(None).screen_uninstall() + self.assertIs(result, tui.Wizard.BACK) + self.assertEqual(flashes[-1], ("qwen-tts uninstalled.", "ok")) + + def test_screen_uninstall_failure_flashes_error(self): + flashes = [] + + def fake_flash(scr, text, kind="warn"): + flashes.append((text, kind)) + + info = BackendInfo("qwen", "qwen-tts", lambda: None, lambda: 0) + with patch.object(hub._Hub, "_pick_backend", return_value=info), \ + patch.object(hub.tui, "confirm", return_value=True), \ + patch.object(hub.taskview, "run_steps", return_value=1), \ + patch.object(info, "uninstall"), \ + patch.object(hub.tui, "flash", fake_flash): + result = hub._Hub(None).screen_uninstall() + self.assertIs(result, tui.Wizard.BACK) + self.assertEqual(flashes[-1][1], "err") + self.assertIn("Could not fully uninstall", flashes[-1][0]) + + def test_screen_uninstall_confirm_declined_skips_uninstall(self): + info = BackendInfo("qwen", "qwen-tts", lambda: None, lambda: 0) + with patch.object(hub._Hub, "_pick_backend", return_value=info), \ + patch.object(hub.tui, "confirm", return_value=False) \ + as mk_confirm, \ + patch.object(hub.taskview, "run_steps") as mk_run, \ + patch.object(info, "uninstall") as mk_uninstall: + result = hub._Hub(None).screen_uninstall() + mk_run.assert_not_called() + mk_uninstall.assert_not_called() + self.assertIs(result, tui.Wizard.BACK) + # The confirm names the backend and is Esc-able (cancel_value set). + self.assertIn("Uninstall qwen-tts?", mk_confirm.call_args[0][1]) + + def test_screen_uninstall_esc_on_confirm_backs_out(self): + info = BackendInfo("qwen", "qwen-tts", lambda: None, lambda: 0) + with patch.object(hub._Hub, "_pick_backend", return_value=info), \ + patch.object(hub.tui, "confirm", + return_value=tui.Wizard.BACK), \ + patch.object(hub.taskview, "run_steps") as mk_run, \ + patch.object(info, "uninstall") as mk_uninstall: + result = hub._Hub(None).screen_uninstall() + mk_run.assert_not_called() + mk_uninstall.assert_not_called() self.assertIs(result, tui.Wizard.BACK) def _capture_flashes(self): |
