diff options
| author | historia <historiavg@proton.me> | 2026-08-27 17:03:57 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-27 17:03:57 -0400 |
| commit | cef2352a5e81b272d067c2c02eb9588e54edfcfd (patch) | |
| tree | 83af416a82d9e854f44b6f3207eb8ea648b0896d /app/tests/test_backends_audiocpp.py | |
| parent | 6527240aa69a08f06e36e796818721abeec9f592 (diff) | |
| download | tts-audiobook-generator-cef2352a5e81b272d067c2c02eb9588e54edfcfd.tar.gz | |
feat: automatic updates added to configure backend menu
Diffstat (limited to 'app/tests/test_backends_audiocpp.py')
| -rw-r--r-- | app/tests/test_backends_audiocpp.py | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/app/tests/test_backends_audiocpp.py b/app/tests/test_backends_audiocpp.py index ab405aa..3e97b82 100644 --- a/app/tests/test_backends_audiocpp.py +++ b/app/tests/test_backends_audiocpp.py @@ -1277,6 +1277,236 @@ class BuildAudiocppTests(unittest.TestCase): self.assertIn("No build script found", notices[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.""" |
